《SQL经典实例》——读书笔记三

第三章:多表查询

3.1 叠加两个行集

你想返回保存在多个表中的数据,理论上需要将一个结果集叠加在另一个之上。这些表可
以没有相同的键,但它们的列的数据类型必须相同。例如,你想显示 EMP 表里部门编号为10 的员工的名字和部门编号,以及 DEPT 表中各个部门的名称和编号。

SELECT ename as ENAME_AND_DNAME,deptno
FROM emp
WHERE deptno=10
UNION ALL
SELECT '-----------',NULL
UNION ALL
SELECT dname,deptno
FROM dept

使用集合运算 UNION ALL 合并多个表中的行,UNION ALL 将多个表中的行并入一个结果集。对于所有的集合运算来说,SELECT 列表里的所有项目必须保持数目相同,且数据类型匹配。尤其需要注意的是,如果有重复项,UNION ALL 也将一并纳入。如果你希望过滤掉重复项,
可以使用 UNION 运算符。

3.2 合并相关行

你想根据一个共同的列或者具有相同值的列做连接查询,并返回多个表中的行。例如,你
想显示部门编号为 10 的全部员工的名字及其部门所在地,但这些数据分别存储在两个表
里。

SELECT e.ename,d.loc
FROM emp as e,dept as d
WHERE e.deptno=d.DEPTNO
AND e.deptno =10

它是内连接中的相等连接。连接查询是一种把来自两个表的行合并起来的操作。对于相等连接而言,其连接条件依赖于某
个相等条件
另一种写法是利用显式的 JOIN 子句(INNER 关键字是可选项)。

SELECT e.ename,d.loc
FROM emp as e INNER JOIN dept as d 
 ON e.deptno=d.DEPTNO
WHERE e.deptno=10

3.3 查找两个表中相同的行

你想找出两个表中相同的行,但需要连接多列。例如,考虑如下所示的视图 V。

create view V
as
select ename,job,sal
 from emp
 where job = 'CLERK'
select * from V
ENAME JOB SAL
---------- --------- ----------
SMITH CLERK 800
ADAMS CLERK 1100
JAMES CLERK 950
MILLER CLERK 1300

视图 V 只包含职位是 CLERK 的员工,但并没有显示 EMP 表中所有可能的列。你想从 EMP 表获取与视图 V 相匹配的全部员工的 EMPNO、ENAME、JOB、SAL 和 DEPTNO。

select e.empno,e.ename,e.job,e.sal,e.deptno
from emp as e,V
where e.ename=V.ename
AND e.job=V.job
AND e.sal=V.sal

除此之外,也可以使用 JOIN 子句执行同样的连接查询。

select e.empno,e.ename,e.job,e.sal,e.deptno
from emp as e inner join V
on (e.ename=V.ename
AND e.job=V.job
AND e.sal=V.sal)

3.4 查找只存在于一个表中的数据

例如,你想找出在 DEPT 表中存在而在 EMP 表里却不存在的部门编号(如果有的话)。

select deptno
from dept
where deptno not in(select 
deptno from emp)

3.5 从一个表检索与另一个表不相关的行

两个表有相同的键,你想在一个表里查找与另一个表不相匹配的行。例如,你想找出哪些
部门没有员工。

select d.*
from dept as d left outer join emp as e
on(e.deptno=d.deptno)
where e.deptno is null

3.6 新增连接查询而不影响其他连接查询

你已经有了一个查询语句,它可以返回你想要的数据。你需要一些额外信息,但当你试图
获取这些信息的时候,却丢失了原有的查询结果集中的数据。例如,你想查找所有员工的
信息,包括他们所在部门的位置,以及他们收到奖金的日期。

SELECT e.ename,d.loc,eb.received
FROM emp as e join dept as d
on(e.deptno=d.deptno)
LEFT join emp_bonus as eb ON
(e.EMPNO=eb.EMPNO)
order by 2

外连接查询会返回一个表中的所有行,以及另一个表中与之匹配的行。

左外连接: 包含左边表的全部行(不管右边的表中是否存在与它们匹配的行),以及右边表中全部匹配的行
右外连接: 包含右边表的全部行(不管左边的表中是否存在与它们匹配的行),以及左边表中全部匹配的行

3.7 确定两个表是否有相同的数据

create view V
as
select * from emp where deptno != 10
 union all
select * from emp where ename = 'WARD'
select * from V
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
----- ---------- --------- ----- ----------- ----- ----- ------
7369 SMITH CLERK 7902 17-DEC-1980 800 20
7499 ALLEN SALESMAN 7698 20-FEB-1981 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-1981 1250 500 30
7566 JONES MANAGER 7839 02-APR-1981 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-1981 1250 1300 30
7698 BLAKE MANAGER 7839 01-MAY-1981 2850 30
7788 SCOTT ANALYST 7566 09-DEC-1982 3000 20
7844 TURNER SALESMAN 7698 08-SEP-1981 1500 0 30
7876 ADAMS CLERK 7788 12-JAN-1983 1100 20
7900 JAMES CLERK 7698 03-DEC-1981 950 30
7902 FORD ANALYST 7566 03-DEC-1981 3000 20
7521 WARD SALESMAN 7698 22-FEB-1981 1250 500 30

你希望确定该视图是否和 EMP 表有完全相同的数据。与员工 WARD 相关的数据有两行,
这表明相应的解决方案不仅要找出来不同的数据,还要找到重复的数据。根据 EMP 表的数据,二者的不同之处包括 3 行部门编号为 10 的数据以及两行员工 WARD 的数据。
使用关联子查询和 UNION ALL 找出那些存在于视图 V 而不存在于 EMP 表的数据,以及存在于 EMP 表而不存在于视图 V 的数据,并将它们合并起来。

select *
from (select e.empno,e.ename,e.job,e.mgr,e.hiredate,
e.sal,e.comm,e.deptno,count(*) as cnt
from emp e
group by e.empno,e.ename,e.job,e.mgr,e.hiredate,
e.sal,e.comm,e.deptno)e     #构建内嵌视图e
where not exists(select NULL
from (select v.empno,v.ename,v.job,v.mgr,v.hiredate,
v.sal,v.comm,v.deptno,count(*) as cnt
from v 
group by v.empno,v.ename,v.job,v.mgr,v.hiredate,
v.sal,v.comm,v.deptno)v     #构建内嵌视图v
where v.empno=e.empno
and v.ename=e.ename
and v.job=e.job
and v.mgr=e.mgr
and v.hiredate=e.hiredate
and v.sal=e.sal
and v.sal=e.sal
and v.deptno=e.deptno
and v.cnt=e.cnt
and coalesce(v.comm,0)=coalesce(e.comm,0))
union ALL
select*
from (select v.empno,v.ename,v.job,v.mgr,v.hiredate,
v.sal,v.comm,v.deptno,count(*)as cnt
from v
group by v.empno,v.ename,v.job,v.mgr,v.hiredate,
v.sal,v.comm,v.deptno)v
where not exists(select NULL
from (select e.empno,e.ename,e.job,e.mgr,e.hiredate,
e.sal,e.comm,e.deptno,count(*) as cnt 
from emp e
group by e.empno,e.ename,e.job,e.mgr,e.hiredate,
e.sal,e.comm,e.deptno)e
WHERE v.empno=e.empno
and v.ename=e.ename
and v.job=e.job
and v.mgr=e.mgr
and v.hiredate=e.hiredate
and v.sal=e.sal
and v.sal=e.sal
and v.deptno=e.deptno
and v.cnt=e.cnt
and coalesce(v.comm,0)=coalesce(e.comm,0))

首先查找存在内嵌视图e,不存在内嵌视图v的行数据,然后查找查找存在内嵌视图v,不存在内嵌视图e的行数据,最后用union all合并,不同的字段为cnt。

3.8 识别并消除笛卡儿积

你想找出部门编号为 10 的所有员工的名字及其部门所在的城市。

select e.ename ,d.loc
from emp e,dept d
where e.deptno=10 AND
e.deptno=d.deptno

3.9 组合使用连接查询与聚合函数

你想执行一个聚合操作,但查询语句涉及多个表。你希望确保表之间的连接查询不会干扰
聚合操作。例如,你希望计算部门编号为 10 的员工的工资总额以及奖金总和。因为有部
分员工多次获得奖金,所以在 EMP 表和 EMP_BONUS 表连接之后再执行聚合函数 SUM,就会得出错误的计算结果。

select e.empno,
 e.ename,
 e.sal,
 e.deptno,
 e.sal*case when eb.type = 1 then .1
 when eb.type = 2 then .2
 else .3
 end as bonus
 from emp e, emp_bonus eb
 where e.empno = eb.empno
 and e.deptno = 10

因为有员工多次获得奖金,所以同一个人的sal列会多次出现,计算工资总和时会多次计算,结果就错了。
连接 EMP_BONUS 表并计算奖金总和,通常有两种办法来使用聚合函数,而且可以避免得出错误的计算结果。一种方法是,调用聚合函数时直接使用关键字 DISTINCT,这样每个值都会先去掉重复项再参与计算;另一种方法是,在进行连接查询之前先执行聚合运算(以内嵌视图的方式),这样可以避免错误的结果,因为聚合运算发生在连接查询之前。下面的解决方案使用了 DISTINCT。

select deptno,sum(distinct sal)as total_sal,sum(bonus)as total_bonus
from(select e.empno,
 e.ename,
 e.sal,
 e.deptno,
 e.sal*case when eb.type = 1 then .1
 when eb.type = 2 then .2
 else .3
 end as bonus
 from emp e, emp_bonus eb
 where e.empno = eb.empno
 and e.deptno = 10) x
group by deptno

另外一种方法是,先计算部门编号为 10 的全部员工的工资总额,作为内置视图d ,然后连接 EMP 表和 EMP_BONUS 表和内置视图d。

select d.deptno,
 d.total_sal,
 sum(e.sal*case when eb.type = 1 then .1
 when eb.type = 2 then .2
 else .3 end) as total_bonus
 from emp e,
 emp_bonus eb,
 (select deptno, sum(sal) as total_sal
 from emp
 where deptno = 10
 group by deptno) d
 where e.deptno = d.deptno
 and e.empno = eb.empno
 group by d.deptno,d.total_sal

3.10 组合使用外连接查询与聚合函数

本节的问题和 3.9 节的大致相同,只是略微修改了 EMP_BONUS 表的数据,使得部门编号为10 的员工中只有部分人获得了奖金。

select deptno,sum(distinct sal)as total_sal,sum(bonus)as total_bonus
from(select e.empno,
 e.ename,
 e.sal,
 e.deptno,
 e.sal*case when eb.type is null then 0
 when eb.type = 1 then .1
 when eb.type = 2 then .2
 else .3
 end as bonus
 from emp as e left outer join emp_bonus as eb
on (e.empno = eb.empno)
where e.deptno = 10) g
group by deptno

下面的查询语句是另一种解决方案。首先计算部门编号为 10 的员工的工资总额,然后再
连接 EMP 表和 EMP_BONUS 表(这样就避免了使用外连接)。

 select d.deptno,
 d.total_sal,
 sum(e.sal*case when eb.type = 1 then .1
 when eb.type = 2 then .2
 else .3 end) as total_bonus
 from emp e,
 emp_bonus eb,
 (
 select deptno, sum(sal) as total_sal
 from emp
 where deptno = 10
 group by deptno
 ) d
 where e.deptno = d.deptno 
 and e.empno = eb.empno
 group by d.deptno,d.total_sal
 

3.11 从多个表中返回缺失值

找到存在于 DEPT 表而不存在于 EMP 表的数据(即没有员工的
部门)需要使用外连接。现在假设有一个员工不属于任何部门,你将如何返回以上
结果集,并且包含那个不属于任何部门的员工呢?换句话说,你希望在同一个查询语句
里既外连接到 EMP 表又外连接到 DEPT 表。

select d.deptno,d.dname,e.ename
from dept d left outer join emp e
 on (d.deptno=e.deptno)
UNION
select d.deptno,d.dname,e.ename
from emp e left outer join dept d
on(d.deptno=e.deptno)

MySql不支持全连接FUll JOIN,不过可以通过联合UNION来模拟。

3.12 在运算和比较中使用Null

ull 不会等于或不等于任何值,甚至不能与其自身进行比较,但是你希望对从 Null 列返
回的数据进行评估,就像评估具体的值一样。例如,你想找出 EMP 表里业务提成(COMM列)比员工 WARD 低的所有员工。检索结果应该包含业务提成为 Null 的员工。

select e.empno,e.ename,e.comm
from emp e
where coalesce(comm,0)<(select e.COMM
from emp e
where e.ename='ward')

使用如 COALESCE 这样的函数把 Null 转换为一个具体的、可以用于标准评估的值。
COALESCE 函数会返回参数列表里的第一个非 Null 值。就本实例而言,COMM 列中的 Null 会
被替换为 0,这样才能与 WARD 的业务提成相比较。

猜你喜欢

转载自blog.csdn.net/weixin_42695959/article/details/88373147