Oracle数据库之七 多表查询

7、多表查询

​ 对于查询在以前已经学过了简单查询、限定查询、查询排序,这些都属于 SQL 的标准语句,而上一章的单行函数,主要功能是为了弥补查询的不足。sql

​ 而从多表查询开始就正式进入到了复杂查询部分。数据库

7.一、基本语法

  • 多表查询就是在一条查询语句中,从多张表里一块儿取出所须要的数据。若是要想进行多表查询,直接在 FROM 子句以后跟上多个表便可,语法以下:函数

    SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
    FROM 表名称1[表别名1],表名称2[表别名2],...
    [WHERE 条件(s)]
    [ORDER BY 排序的字段1 ASC|DESC,排序的字段2 ASC|DESC,...];
  • 下面就将采用 emp 表和 dept 表一块儿进行多表查询,查询以前多作一个步骤,先肯定 emp 和 dept 表中的数据量分别有多少,可使用 COUNT() 函数统计性能

范例:统计 emp 表中的数据量 (14 行记录)学习

SELECT COUNT(*) 
FROM emp;

范例:统计 dept 表中的数据量 (4行记录)spa

SELECT COUNT(*) 
FROM dept;

范例:如今查询全部的雇员和部门的所有详细信息code

SELECT COUNT(*) 
FROM emp,dept;
  • 发现此时结果一共返回了56行记录,这就是笛卡尔积 形成的问题。如今就发现了,笛卡尔积的出现,可让查询结果变得很是的庞大,若是如今两张表的数据量都很大,那么这种庞大是很可怕的,因此如今必须想办法消除掉笛卡尔积。
  • 通常而言,若是要想进行笛卡尔积的消除,每每会使用关联字段,因为多张表之间可能会存在重名的字段,因此进行重名字段的访问的时候看,前面须要加上表名称,采用 “ 表名称 字段 ” 的方式来进行访问。

范例:利用等值条件来处理笛卡尔积排序

SELECT * 
FROM emp,dept
WHERE emp.deptno = dept.deptno;
  • 这时的结果就能够发现已经消除掉笛卡尔积,可是这时积依然存在,只是不显示了而已
  • 已经清楚基本概念后,下面就能够针对于数据量作一个分析。在 Oracle 中存在了一个 sh 用户,固然此用户保存在了 pdbmldn 插入式数据库之中了
    • 一、打开 sqlplus :运行 - 输入 sqlplus /nolog
    • 二、管理员链接数据库:conn sys/change_on_install AS SYSDBA;
    • 三、切换到 pdbmldn 数据库:ALTER SESSION SET CONTAINER=pdbmldn;
    • 四、打开 pdbmldn 数据库:ALTER DATABASE pdbmldn OPEN
    • 立刻就要使用 sh 用户进行操做,如今可使用 sh 用户下的 sales 和 costs 表,这两张表的数量都比较大。

范例:查看 sh.sales 表的数据量 (918843条记录)开发

SELECT COUNT(*)
FROM sh.sales;

范例:查看 sh.costs 表的数据量 (82112条记录)学习资料

SELECT COUNT(*)
FROM sh.costs;

范例:若是如今直接将这两张表进行多表查询,那么来观察问题

SELECT COUNT(*)
FROM sh.sales,sh.costs;
  • 一旦开始执行以后,那么这个等待的过程会很长,可是这时是显示全部数据量(包含笛卡尔积的数据量),然后再开始消除,利用等值关联。最终会返回的数据量:75,448,036,416
  • 虽然消除掉了全部显示的笛卡尔积,可是数据库的原理机制及表示笛卡尔积会永远存在
  • 多表查询会产生笛卡尔积,因此性能较差,能够利用等值关联字段消除笛卡尔积

7.二、多表查询实例

  • 虽然多表查询自己存在了性能问题,但并不表示多表查询没法使用,须要一些更合理的作法来解决多表查询问题

范例:查询每一个雇员的编号、姓名、职位、基本工资、部门名称、部门位置信息

  • 肯定所须要的数据表
    • emp 表:查询每一个雇员的编号、姓名、职位、基本工资;
    • dept 表:部门名称,部门位置。
  • 肯定已知的关联字段
    • 部门与雇员关联:emp.deptno = dept.deptno
  • 随后还须要按照一个 SQL 语句的执行步骤编写:FROM , WHERE , SELECT 。
SELECT emp.empno,emp.ename ,emp.job,emp.sal,dept.dname,dept.loc
FROM emp,dept
WHERE emp.deptno = dept.deptno
  • 可是在此处就会存在一个问题,上面程序里都是采用了表名称访问的列名称,若是如今表名称很长:yuzhou_yinhexi_diqiu_yazhou_zhongguo_beijing 。因此每每在多表查询的时候为查询的数据定义别名,而别名也是在 FROM 子句之中定义的,上面的程序能够改写为:
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.local AND salgrade.hisal ;
SELECT e.empno, e.ename, e.hiredate, e.sal, s.grade
FROM emp e, salgrade s 
WHERE e.sal BETWEEN s.losal AND s.hisal;
  • 在以前的查询里面,发现只是显示了数字1,2,3,4,5,如今但愿能够将其替换为中文,若是要替换,确定使用 DECODE() 函数。

范例:为了更加清楚的显示出工资等级的信息,如今但愿能够按以下格式进行替换显示:

​ grade = 1 : 显示为 “ E等工资 ”

​ grade = 2 : 显示为 “ D等工资 ”

​ grade = 3 : 显示为 “ C等工资 ”

​ grade = 4 : 显示为 “ B等工资 ”

​ grade = 5 : 显示为 “ A等工资 ”

SELECT e.empno, e.ename, e.hiredate, e.sal,
	DECODE(s.grade,1,'E等工资',2,'D等工资',3,'C等工资',4,'B等工资',5,'A等工资') grade
FROM emp e, salgrade s 
WHERE e.sal BETWEEN s.losal AND s.hisal;
  • 前面讲的都是针对于两张表进行多表查询,并且多表查询里面都只使用了一个条件来消除笛卡尔积;若是如今是多个消除笛卡尔积的条件,那么每每使用 AND 将这些条件链接在一块儿

范例:查询出每一个雇员的姓名、职位、基本工资、部门名称、工资等级

  • 肯定所须要的数据表
    • emp 表:每一个雇员的姓名、职位、基本工资;
    • dept 表:部门名称
    • salgrade 表:工资等级。
  • 肯定已知的关联字段
    • 雇员和部门:emp.deptno = dept.deptno
    • 雇员和工资等级:emp.sal BETWEEN salgrade.local AND salgrade.hisal ;
SELECT e.ename, e.job, e.sal, d.dname,
	DECODE(s.grade,1,'E等工资',2,'D等工资',3,'C等工资',4,'B等工资',5,'A等工资') grade
FROM emp e, dept d, salgrade s 
WHERE e.deptno = d.deptno AND e.sal BETWEEN s.losal AND s.hisal;
  • 多表查询之中,每当增长一个关联表都须要设置消除笛卡尔积的条件

7.三、表的链接操做

  • 对于数据表的链接操做在数据库中一共定义了两种:
    • 内链接:也称为等值链接(或称为链接,还能够被称为普通链接或者天然链接),是最先的一种链接方式,内链接是从结果表中删除与其余被链接表中没有匹配行的全部元组,因此当匹配条件不知足时内链接可能会丢失信息。在以前所使用的链接方式都属于内链接,而在 WHERE 子句之中设置的消除笛卡尔积的条件就是采用了等值判断的方式进行的。
    • 外链接:内链接中只可以显示等值知足的条件,若是不知足的条件则没法显示,若是如今但愿特定表中的数据能够所有显示,就利用外链接,外链接分为三种:左外链接(简称:左链接)、右外链接(简称:右链接)、全外链接(简称:全链接,在 SQL :1999 语法部分讲解)
  • 在以前所编写的表链接操做都属于内链接的定义范畴
  • 为了更好的观察各个链接方式的区别,首先须要在 emp 表中增长一条数据,这个增长语法会在后面解释
INSERT INTO emp(empno, ename, job, mgr, hiredate, sal, comm, deptno) 
	VALUES(8888,'李华华','CLERK',7369,SYSDATE,800,100,null);
  • 这时增长完的数据是一个没有部门的雇员,即此雇员的部门编号是 null ,增长完成以后查看一下 emp 表中当前的所有数据
SELECT * FROM emp;
  • 此处没有部门编号,下面就演示等值链接所带来的效果

范例:使用等值链接

SELECT *
FROM emp e, dept d
WHERE e.deptno = d.deptno;
  • 经过此时的结果能够发现两个问题:
    • 问题一:没有部门的雇员没有显示
    • 问题二:有一个40部门没有显示
  • 因此如今就能够发现,使用内链接只有知足链接条件的数据才会所有显示。但是若是说如今但愿 emp 或是 dept 表中的数据显示完整,就能够利用外链接进行。
  • 外链接如今主要使用两种:
    • 左外链接:左关系属性 = 右关系属性(+) ,+ 放在了等号的右边,表示左链接;
    • 右外链接:左关系属性(+) = 右关系属性 ,+ 放在了等号的左边,表示右链接。

范例:使用左外链接,显示雇员编号是8888的信息

SELECT *
FROM emp e, dept d 
WHERE e.deptno = d.deptno(+);

范例:使用右外链接,显示部门编号为40的信息

SELECT *
FROM emp e, dept d 
WHERE e.deptno(+) = d.deptno;
  • 上面结果中由于40部门没有雇员,因此全部的雇员信息都为 null 。
  • 我的总结:使用外链接的环境,若是所须要的数据信息没有显示出来,那么就使用外链接,而具体是左外仍是右外,我的认为不必去记,去试就行!

7.四、自身关联

  • 在 emp 表中存在有一个 mgr 字段,这个字段表示的是雇员的领导
SELECT * FROM emp;
  • 如今若是要显示雇员的领导信息,那么确定利用雇员表和雇员表本身的链接操做完成。利用雇员表中的领导编号,找到对应此编号的雇员信息。

范例:查询出每一个雇员的编号、姓名及其上级领导的编号、姓名

  • 肯定所须要的数据表
    • emp 表:雇员的编号、姓名
    • emp 表:找到领导的编号、姓名
  • 肯定已知的关联字段
    • 雇员和领导:emp.mgr = memp.empno (雇员的领导编号 = 领导的信息)
  • 步骤一:直接进行自身链接的操做
SELECT e.empno eno, e.ename ename, m.empno mno, m.ename mname
FROM emp e, emp m 
WHERE e.mgr = m.empno;

​ 如今表中一共有15条记录,可是只有14条的记录显示,等值链接在没有条件知足的时候,是不可能有数据显示的。

​ 在 emp 表中 king 这个雇员是没有领导的,这个时候就必须考虑外链接。

  • 步骤二:使用左外链接
SELECT e.empno eno, e.ename ename, m.empno mno, m.ename mname
FROM emp e, emp m 
WHERE e.mgr = m.empno(+);

​ 对于没有领导信息的雇员,对应的领导信息,所有使用 null 进行表示

范例:查询出在1981年雇员的所有雇员的编号、姓名、雇佣日期(按照年-月-日显示)、工做、领导姓名、雇员月工资、雇员年工资(基本工资+奖金)、雇员工资等级、部门编号、部门名称、部门位置、而且要求这些雇员的月基本工资在1500~3500之间,将最后的结果按照年工资的降序排列,若是年工资相等,则按工做进行排序

  • 肯定所须要的数据表
    • emp 表:编号、姓名、雇佣日期、工做、月工资、计算年工资;
    • emp 表:领导姓名;
    • dept 表:部门编号、名称、位置;
    • salgrade 表:工资等级。
  • 肯定已知的关联字段
    • 雇员和领导:emp.mgr = memp.empno;
    • 雇员和部门:emp.deptno = dept.deptno;
    • 雇员和工资等级:emp.sal BETWEEN salgrade.losal AND salgrade.hisal .
  • 步骤一:查询出全部在1981年雇佣的雇员编号、姓名、雇佣日期、工做、月工资、年工资、而且月薪在1500~3500之间。只须要 emp 单张表便可实现。
SELECT e.empno, e.ename, e.hiredate, e.sal, (e.sal+NVL(e.comm,0))*12 income
FROM emp e 
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500;
  • 步骤二:加入领导信息,使用自身关联
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
	m.ename mname
FROM emp e, emp m 
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
	AND e.mgr = m.empno(+);
  • 步骤三:加入部门信息
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
	m.ename mname, d.deptno dno, d.dname dname, d.loc 
FROM emp e, emp m, dept d  
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
	AND e.mgr = m.empno(+)
	AND e.deptno = d.deptno;
  • 步骤四:查询出工资等级
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
	m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade
FROM emp e, emp m, dept d,salgrade s   
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
	AND e.mgr = m.empno(+)
	AND e.deptno = d.deptno
	AND e.sal BETWEEN s.losal AND s.hisal;
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
	m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade,
	DECODE(s.grade,1,'E等工资',2,'D等工资',3,'C等工资',4,'B等工资',5,'A等工资') 工资等级
FROM emp e, emp m, dept d,salgrade s   
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
	AND e.mgr = m.empno(+)
	AND e.deptno = d.deptno
	AND e.sal BETWEEN s.losal AND s.hisal;
  • 步骤五:进行排序,因为 SELECT 是在 ORDER BY 子句以前执行,因此在 SELECT 子句之中所定义的别名,ORDER BY 子句是能够直接使用的。
SELECT e.empno, e.ename, e.hiredate, e.job, e.sal, (e.sal+NVL(e.comm,0))*12 income,
	m.ename mname, d.deptno dno, d.dname dname, d.loc, s.grade,
	DECODE(s.grade,1,'E等工资',2,'D等工资',3,'C等工资',4,'B等工资',5,'A等工资') 工资等级
FROM emp e, emp m, dept d,salgrade s   
WHERE TO_CHAR(e.hiredate,'yyyy') = '1981' AND e.sal BETWEEN 1500 AND 3500
	AND e.mgr = m.empno(+)
	AND e.deptno = d.deptno
	AND e.sal BETWEEN s.losal AND s.hisal;
ORDER BY income DESC, e.job;
  • 经过这一稍微复杂点的题目,能够发现,全部的分析必需要分步进行,并且这些分析过程,须要大量的练习才能够巩固。
  • 自身关联属于一张表本身关联本身的状况,此时依然会产生笛卡尔积。

7.五、SQL:1999 语法的支持

  • 在以前使用的 “ (+) ” 标记只适合在 Oracle 数据库之中应用,若是是其余的数据库,是没法使用的
  • SQL:1999 语法
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [CROSS JOIN 表2 表别名2]|
[NATURAL JOIN 表2 表别名2]|
[JOIN 表2 USING(关联列名称)]|
[JOIN 表2 ON(关联条件)]|
[LEFT|RIGHT|FULL OUTER JOIN 表2 ON(关联条件)]
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];
  • 实际上以上给出的是综合语法,下面将拆分进行讲解

7.5.1 交叉链接

  • 交叉链接(CROSS JOIN)做用于两个关系上,而且第一个关系的每一个元组与第二个关系的全部元组进行链接,这样的操做形式与笛卡尔积是彻底相同的,交叉链接的语法以下所示:
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [CROSS JOIN 表2 表别名2]|
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];
  • 交叉链接的主要功能就是产生笛卡尔积

范例:使用交叉链接查询信息

SELECT * 
FROM emp CROSS JOIN dept;
  • 通常而言,在进行多表链接的时候都必定会存在关联字段以消除笛卡尔积,而关联字段的名称通常都会同样,若是不同,也会有部分相同,如今讨论的是同样的状况,例如 deptno 字段这就表示同样,就能够利用天然链接来消除掉笛卡尔积

7.5.2 天然链接

  • 天然链接(NATURAL JOIN)运算做用于两个关系,最终会经过两个关系产生出一个关系做为结果。与交叉链接(笛卡尔积)不一样的是,天然链接只考虑那些在两个关系模式中都出现的属性上取值相同的元组对。天然链接的操做语法以下:
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [NATURAL JOIN 表2 表别名2]|
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];

范例:使用天然链接查询信息

SELECT * 
FROM emp NATURAL JOIN dept;
  • 这个时候会把链接的字段放在第一列上进行显示,并且这种方式就是一种内链接的方式。

7.5.3 USING 子句

  • 经过天然链接能够直接使用关联字段消除掉笛卡尔积,那么若是如今的两张表中没有存在这种关联字段的话,就能够经过 USING 子句完成笛卡尔积的消除,USING 子句的语法以下:
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [JOIN 表2 USING(关联列名称)]|
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];

范例:使用 USING 子句查询信息

SELECT * 
FROM emp JOIN dept USING(deptno);

7.5.4 ON 子句

  • 在以前编写等值链接时,采用了关联字段进行笛卡尔积的消除,那么用户在 SQL:1999 语法之中经过 ON 子句就能够由用户手工设置一个关联条件,ON 子句语法以下:
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [JOIN 表2 ON(关联条件)]|
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];
  • USING 是设置链接字段,而 ON 是设置链接条件

范例:使用 ON 子句查询信息

SELECT * 
FROM emp e JOIN salgrade s ON(e.sal BETWEEN s.losal AND s.hisal);

7.5.5 外链接

  • 在数据的查询操做中数据的外链接一共分为三种形式:左外链接、右外链接、全外链接,而此时链接的语法以下:
SELECT [DISTINCT] *|列名称 [AS][列别名],列名称 [AS][列别名],...
FROM 表1 表别名1 [LEFT|RIGHT|FULL OUTER JOIN 表2 ON(关联条件)]
[WHERE 条件(s)]
[ORDER BY 排序的字段1 ASC|DESC, 排序的字段2 ASC|DESC,...];
  • 对于外链接,在以前使用的是 “ (+) ”,这个标记只可以实现左外或者右外链接,可是对于全外链接没法使用,而全外链接只可以依靠 SQL:1999 语法之中规定的内容。

范例:实现右外链接

SELECT * 
FROM emp e RIGHT OUTER JOIN dept d ON (e.deptno = d.deptno);

范例: 实现左外链接

SELECT * 
FROM emp e LEFT OUTER JOIN dept d ON (e.deptno = d.deptno);

范例:实现全外链接

SELECT * 
FROM emp e FULL OUTER JOIN dept d ON (e.deptno = d.deptno);
  • 经过以上的分析能够发现,给出的全部语法里面,只有全外链接只可以经过 SQL:1999 语法实现,可是对于这种全外链接,使用的状况并很少。
  • 并且我的建议,若是使用的是 Oracle 数据库,就使用 “ (+) ” 标记控制左右链接,不使用它实现内链接。

7.六、数据的集合运算

  • 数据的集合操做指的是查询结果的操做
  • 集合运算是一种二目运算符,一共包括四种运算符:并、差、交、笛卡尔积,其中对于笛卡尔积在以前已经演示过了,因此本次主要是看 并、交、差 三种操做。操做集合的语法以下
查询语句
	[UNION | UNION ALL | INTERSECT | MINUS]
查询语句
	...
  • 要实现集合的运算,主要使用四种运算符:
    • UNION (并集):返回若干个查询结果的所有内容,可是重复元组不显示;
    • UNION ALL (并集):返回若干个查询结果的所有内容,重复元组也会显示;
    • MINUS (差集):返回若干个查询结果中的不一样部分
    • INTERSECT (交集):返回若干个查询结果中的相同部分

7.6.1 并集操做

  • 并集操做是将多个查询的结果链接到一块儿,而对于并操做提供了两种操做符:UNION(不显示重复),UNION ALL(显示重复)

范例:并集操做:UNION , UNION ALL

  • 第一个查询
SELECT * FROM dept;
  • 第二个查询
SELECT * FROM dept WHERE deptno = 10;

​ 这个时候两个查询结果返回的列的结构相同。

  • 使用 UNION
SELECT * FROM dept
	UNION
SELECT * FROM dept WHERE deptno = 10;

​ 第一个查询已经包含了第二个查询的内容,因此重复数据不显示了。

范例:使用 UNION ALL 显示所有

SELECT * FROM dept
	UNION ALL
SELECT * FROM dept WHERE deptno = 10;
  • **提示:**在之后进行查询操做编写过程当中,建议尽可能使用UNION 或 UNION ALL 来代替 OR

范例:查询全部销售人员和办事人员的信息

  • 实现一:
SELECT * FROM emp WHERE job = 'SALESMAN' OR job = 'CLERK';
  • 实现二:
SELECT * FROM emp WHERE job IN ('SALESMAN', 'CLERK');
  • 实现三:
SELECT * FROM emp WHERE job = 'SALESMAN'
	UNION
SELECT * FROM emp WHERE job = 'CLERK'

7.6.2 差集操做

范例:使用 MINUS 执行差集操做

SELECT * FROM dept
	MINUS
SELECT * FROM dept WHERE deptno = 10;
  • 结果就显示3行,deptno=10 的不显示了

7.6.3 交集操做

范例:使用 INTERSECT 执行交集操做

SELECT * FROM dept
	INTERSECT
SELECT * FROM dept WHERE deptno = 10;
  • 结果只显示 deptno=10 一行了
  • 集合操做时,各个查询语句返回的结构要求一致。

说明:本学习资料是根据李兴华的Oracle开发实战经典整理

相关文章
相关标签/搜索