4.1、多表查询的基本概念
在之前所进行的查询之中可以发现FROM子句之中只会存在有一张数据表,所以之前都只是针对於单表查询操作,而所谓的多表查询指的是同时从多张数据表之中取出数据实现的查询,重点修改的是FROM子句,多表查询的语法如下:
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,...
FROM 数据表 [别名] , 数据表[别名] ,...
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,...]
那么下面先通过一个简单的查询来观察一下多表查询所带来的问题。那么首先来介绍一个“COUNT()”函数,此函数的主要功能是统计一张数据表之中所包含的数据量。
范例:统计emp表的数据量 —— 14条
SELECT COUNT(*) FROM emp ;
范例:统计dept表的数据量 —— 4条
SELECT COUNT(*) FROM dept ;
范例:将emp表和dept表实现多表查询
SELECT * FROM emp ,dept ;
此时的查询符合于之前给出的多表查询的语法要求。但是结果却出现了56行的记录,而这56行记录之中发现每行的记录都出现了重复,即:56行记录= emp表的14行记录 * dept表的4行记录。
之所以出现这样的结果主要是由于数据库的数学公式所导致的。
以上的结果实际上就属于两个集合的“积”,而这种积在数据库之中被称为“笛卡尔积”。那么现在该如何去消除掉这样的“笛卡尔积”呢?唯一的方案就是使用关联字段。
此时应该采用的是emp表中的deptno字段与dept表中的deptno字段进行比较之后才可以得出正确的显示结果。
范例:解决笛卡尔积
SELECT *
FROM emp ,dept
WHERE emp.deptno=dept.deptno ;
此时虽然消除了显示的笛卡尔积,但是笛卡尔积依然是存在的,因为这个是由数据库本身的数学公式所决定的先天性问题。
在进行多表查询的时候一定会存在有关联列,如果表之中存在有同名的列,那么一定要使用“表名称.字段”进行标记。可是如果表名称过长“yuzhou_yinhexi_diqiu_yazhou_zhongguo_dalu_beijing”,那么往往会为其定义一个别名。
范例:使用别名
SELECT *
FROM emp e,dept d
WHERE e.deptno=d.deptno ;
范例:要求查询出每个雇员的编号、姓名、职位、工资、部门名称、部门位置
· 确定要使用的数据表:
|- emp表:雇员的编号、姓名、职位、工资;
|- dept表:部门名称、部门位置;
· 确定已知的关联条件:
|- 雇员和部门:emp.deptno = dept.deptno。
第一步:查询出每个雇员的编号、姓名、职位、工资,只需要emp一张数据表即可
SELECT e.empno,e.ename,e.job,e.sal
FROM emp e ;
第二步:加入部门表,一旦加入了部门表,那么就表示需要增加一个消除笛卡尔积的条件
SELECT e.empno,e.ename,e.job,e.sal,d.dname,d.loc
FROM emp e , dept d
WHERE e.deptno=d.deptno ;
范例:查询出每个雇员的编号、姓名、职位、工资、雇佣日期、工资等级
· 确定要使用的数据表:
|- emp表:雇员的编号、姓名、职位、工资、雇佣日期;
|- salgrade表:工资等级;
· 确定已知的关联条件:
|- 雇员与工资等级:emp.sal BETWEEN salgrade.losal AND salgrade.hisal;
第一步:查询雇员数据
SELECT e.empno,e.ename,e.job,e.sal,e.hiredate
FROM emp e ;
第二步:加入工资等级表,此时使用的是一个范围的查询
SELECT e.empno,e.ename,e.job,e.sal,e.hiredate,s.grade
FROM emp e ,salgrade s
WHERE e.sal BETWEEN s.losal AND s.hisal ;
范例:查询每个雇员的编号、姓名、职位、雇佣日期、工资、工资等级、所在部门名称及位置
· 确定要使用的数据表:
|- emp表:雇员的编号、姓名、职位、雇佣日期、工资;
|- salgrade表:工资等级;
|- dept表:部门名称及位置。
· 确定已知的关联条件:只要是消除笛卡尔积的条件之间都使用AND连接
|- 雇员和工资等级:emp.sal BETWEEN salgrade.losal AND salgrade.hisal;
|- 雇员和部门:emp.deptno = dept.deptno。
第一步:查询每个雇员的编号、姓名、职位、雇佣日期、工资
SELECT e.empno,e.ename,e.job,e.sal,e.hiredate
FROM emp e ;
第二步:增加工资等级的判断
SELECT e.empno,e.ename,e.job,e.sal,e.hiredate,s.grade
FROM emp e ,salgrade s
WHERE e.sal BETWEEN s.losal AND s.hisal ;
第三步:增加部门表信息
SELECTe.empno,e.ename,e.job,e.sal,e.hiredate,s.grade,d.dname,d.loc
FROM emp e ,salgrade s,dept d
WHERE e.sal BETWEEN s.losal AND s.hisal
AND e.deptno=d.deptno ;
但是此时一定要注意, 表关联的越多实际上产生笛卡尔积的数量也越多,就拿以上的查询来讲,实际上最终的数据量:emp表的14行 *dept表的4行 * salgrade表5行 = 280条数据(其中只有14行有用)。
说明:关于数据量庞大的两个说明(以SH用户登录)
· 说明一:在使用NOT IN查询的时候里面不能有NULL
如果说现在拿到一张数据表(选择的costs表,这张表第一次接触)。如果是一位菜鸟使用此表,往往会直接发出如下的一行指令:
SELECT * FROM costs ;
于是此时就悲哀了,如果此表之中的数据量较大,那么最轻的后果是你查看不方便,而严重的后果是死机(中国银行的数据库死机一秒种会有一群人被干掉的)。但是如果是有经验的开发者,往往都先使用COUNT()函数确定一下表中的数据量。如果数据量较大,那么使用特定的操作取出特定的几行数据,如果数据量小,则随便操作。
SELECT COUNT(*) FROM costs ;
那么回到NOT IN之中不能出现null的问题,如果NOTIN里面有了null则表示不为空,那么有些字段的数据是永恒不能为空的,那么就表示查询全部了。如果表中的数据量庞大了,那么直接造成死机。
· 说明二:以上的查询只是消除了显示中的笛卡尔积,但是笛卡尔积仍然存在,下面来验证一下笛卡尔积影响。
SELECT COUNT(*) FROM costs; è 82112条记录
SELECT COUNT(*) FROM sales; è 918843条记录
SELECT COUNT(*)
FROM costs c,sales s
WHERE c.prod_id=s.prod_id; è 75,448,036,416参与计算,保留1,165,337,550
虽然最终的操作已经成功的消除掉了显示的笛卡尔积,但是遗憾的是整个运算执行过程非常的缓慢,所以在实际的工作之中,尤其是在你进行数据表设计的时候应该尽可能的避免查询有可能出现的笛卡尔积问题。
结论:在数据量大的时候绝对不要采用多表查询,而且就算是数据量小,也别用多表查询。
最基础的解决性能问题的方案(面试的时候别说,丢人):
例如:现在要求查询出每个雇员的姓名、职位、工资、部门名称
这样的查询一定要使用emp和dept两张表共同完成,但是如果假设emp表中有200W个雇员,而dept表中有5W个部门,那么此时如果直接关联产生的笛卡尔积相当的庞大,所以在这个时候最好的解决方案就是在设计初期,在emp表里面重复保存一个部门名称(重复的)。
4.2、连接方式
对于多表查询来讲其有两种表的连接方式:
· 内连接:也被称为等值连接,在之前的所有查询都属于内连接;
· 外连接:左外连接、右外连接、全外连接。
那么为了更好的观察出内连接与外连接区别,首先为emp表中增加一行没有部门的雇员。
范例:增加一个没有部门的数据
INSERT INTO emp(empno,ename,job,hiredate,sal)
VALUES (7777,'刘小忙','CLERK',SYSDATE,701) ;
此时新增加的数据所在的部门为null。
范例:观察内连接
SELECT *
FROM emp e,dept d
WHERE e.deptno=d.deptno ;
因为7777的部门编号为null,所以过滤条件(WHERE e.deptno=d.deptno)不满足。
如果要想让没有部门的雇员或者是没有雇员的部门数据显示,则可以采用如下的方式实现外连接:
· 左外连接:字段 = 字段(+),“(+)”在等号右边表示左外连接;
· 右外连接:字段(+) = 字段,“(+)”在等号左边表示右外连接;
范例:观察左外连接 —— 7777的雇员数据显示了,对应的部门信息为null
SELECT *
FROM emp e,dept d
WHERE e.deptno=d.deptno(+) ;
范例:观察右外连接 —— 40部门显示了,对应的雇员信息为null
SELECT *
FROM emp e,dept d
WHERE e.deptno(+)=d.deptno ;
一般而言,如果现在需要查询的数据没有出现,才会使用到外连接,没有必要去强制性的去分清楚到底是左还是右。
范例:查询每个雇员的编号、姓名、职位、领导姓名
实际上在emp表之中存在有一个mgr的字段,这个字段保存的是每一位雇员对应的领导编号。
· 确定所需要的数据表:
|- emp表:雇员的编号、姓名、职位;
|- emp表:领导姓名;
· 确定已知的关联字段:
|- 雇员和领导:emp.mgr = memp.empno(雇员的领导编号 = 雇员编号)。
第一步:查询emp表
SELECT e.empno,e.ename,e.job
FROM emp e ;
第二步:查询出领导的姓名 è 内连接,等值连接,有null的不显示
SELECT e.empno,e.ename,e.job,m.ename
FROM emp e ,emp m
WHERE e.mgr=m.empno;
第三步:加入外连接控制,让所有的雇员数据显示
SELECT e.empno,e.ename,e.job,m.ename
FROM emp e ,emp m
WHERE e.mgr=m.empno(+);
在实际的工作之中外连接在多表查询的操作里面概念非常的重要,而且以上的表属于自身关联查询。
4.3、SQL:1999语法支持
SQL语法标准实际上一直在进行更新,从1999年之后对于数据表的关联查询给了一个标准的操作语法(因为“(+)”符号只有Oracle可以使,那么其它的数据库不支持此类符号,只能够使用SQL:1999语法完成),此类语法定义如下:
SELECT * | 字段 [别名]
FROM 表 [CROSS JOIN 表] [NATURAL JOIN 表]
[JOIN] [USING(字段)] [ON(条件)]
[LEFT | RIGTH | FULL | OUTER JOIN 表] ;
以上给出的是一个完整语法,实际上这些语法都可以进行细分。
1、 交叉连接,其主要的目的是为了产生笛卡尔积
语法:
SELECT * | 字段 [别名] FROM 表 CROSS JOIN 表 ;
范例:
SELECT * FROM emp CROSS JOIN dept ;
2、 自然连接,主要是消除掉笛卡尔积(内连接)
语法:
SELECT * | 字段 [别名] FROM 表 NATURAL JOIN 表 ;
范例:
SELECT * FROM emp NATURAL JOIN dept ;
3、 使用USING子句,用户指定关联字段
语法:
SELECT * | 字段 [别名] FROM 表JOIN 表 USING(字段);
范例:
SELECT * FROM emp JOIN dept USING(deptno) ;
4、 使用ON子句指定关联条件
语法:
SELECT * | 字段 [别名] FROM 表JOIN 表 ON(条件);
范例:
SELECT * FROM emp e JOIN dept d ON(e.deptno=d.deptno) ;
5、 外连接
语法:
SELECT * | 字段 [别名] FROM 表 LEFT | RIGTH | FULL OUTER JOIN 表];
范例:
SELECT * FROM emp e LEFT OUTER JOIN dept d ON(e.deptno=d.deptno);
SELECT * FROM emp e RIGHT OUTER JOIN dept d ON(e.deptno=d.deptno);
SELECT * FROM emp e FULL OUTER JOIN dept d ON(e.deptno=d.deptno);
如果要想使用全外连接只能够利用以上的语法完成。
4.4、查询结果连接
在数学之中存在交集、并集、差集的概念,那么此概念在SQL语句之中也同样存在。在SQL语法之中提供了:UNION、UNIONALL、INTERSECT、MINUS几个符号实现结合操作,这几个符合可以连接多个SQL,使用的基本语法如下:
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,...
FROM 数据表 [别名] , 数据表 [别名] ,...
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,...]
[UNION | UNION ALL | INTERSECT | MINUS]
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,...
FROM 数据表 [别名] , 数据表 [别名] ,...
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,...]
[UNION | UNION ALL | INTERSECT | MINUS]
SELECT [DISTINCT] * | 列名称 [别名] , 列名称 [别名] ,...
FROM 数据表 [别名] , 数据表 [别名] ,...
[WHERE 条件(s)]
[ORDER BY 字段 [ASC | DESC] , 字段 [ASC | DESC] ,...]
....
范例:验证UNION操作,不显示重复记录
SELECT * FROM emp WHERE deptno=10
UNION
SELECT * FROM emp ;
范例:验证UNION ALL操作,显示所有数据,包含重复数据
SELECT * FROM emp WHERE deptno=10
UNION ALL
SELECT * FROM emp ;
范例:验证INTERSECT操作,返回相同的部分,交集
SELECT * FROM emp WHERE deptno=10
INTERSECT
SELECT * FROM emp ;
范例:验证MINUS操作,返回不同的部分,是一个差集
SELECT * FROM emp
MINUS
SELECT * FROM emp WHERE deptno=10 ;
在工作之中有部分的查询会利用以上的方式以简化查询的复杂度。
转载自 weico 作者:MLDN李兴华 地址:http://weibo.com/p/1035052004834713/wenzhang?cfs=600&Pl_Core_ArticleList__66_filter=&Pl_Core_ArticleList__66_page=11#Pl_Core_ArticleList__66