目录
-
- [1. 基本查询回顾](#1. 基本查询回顾)
- [2. 多表查询](#2. 多表查询)
-
- [2.1 笛卡尔积](#2.1 笛卡尔积)
- [2.2 显示雇员名、雇员工资以及所在部门的名字](#2.2 显示雇员名、雇员工资以及所在部门的名字)
- [2.3 显示部门号为 10 的部门名、员工名和工资](#2.3 显示部门号为 10 的部门名、员工名和工资)
- [2.4 显示各个员工的姓名、工资及工资级别](#2.4 显示各个员工的姓名、工资及工资级别)
- [3. 自连接](#3. 自连接)
- [4. 子查询](#4. 子查询)
-
- [4.1 单行子查询](#4.1 单行子查询)
- [4.2 多行子查询](#4.2 多行子查询)
- [4.3 多列子查询](#4.3 多列子查询)
- [4.4 在 FROM 子句中使用子查询](#4.4 在 FROM 子句中使用子查询)
- [5. 合并查询](#5. 合并查询)
-
- [5.1 UNION](#5.1 UNION)
- [5.2 UNION ALL](#5.2 UNION ALL)
前面我们讲解的 MySQL 查询大多基于单张表,但在实际开发中,数据往往分散在多张表中。本文从基本查询回顾出发,逐步深入到多表查询、自连接、子查询、合并查询,配合大量案例与执行结果,带你彻底掌握 MySQL 复合查询,让复杂业务查询不再成为难题!
1. 基本查询回顾
在进入复合查询之前,先快速回顾几个常用单表查询案例。
查询工资高于 500 或岗位为 MANAGER 的雇员,同时姓名首字母为大写:
sql
select * from EMP
where (sal > 500 or job = 'MANAGER') and ename like 'J%';
按照部门号升序、雇员工资降序排序:
sql
select * from EMP order by deptno, sal desc;
使用年薪进行降序排序:
sql
select ename, sal * 12 + ifnull(comm, 0) as '年薪'
from EMP
order by 年薪 desc;
显示工资最高的员工的名字和工作岗位:
sql
select ename, job from EMP
where sal = (select max(sal) from EMP);
显示工资高于平均工资的员工信息:
sql
select ename, sal from EMP
where sal > (select avg(sal) from EMP);
显示每个部门的平均工资和最高工资:
sql
select deptno, format(avg(sal), 2), max(sal)
from EMP
group by deptno;
显示平均工资低于 2000 的部门号和它的平均工资:
sql
select deptno, avg(sal) as avg_sal
from EMP
group by deptno
having avg_sal < 2000;
显示每种岗位的雇员总数、平均工资:
sql
select job, count(*), format(avg(sal), 2)
from EMP
group by job;
这些单表查询是复合查询的基础,接下来进入多表查询。
2. 多表查询
实际开发中,数据往往来自不同的表,因此需要多表联合查询。本节以公司管理系统为例,涉及三张表:EMP、DEPT、SALGRADE。
2.1 笛卡尔积
多表查询时,如果不加过滤条件,会得到两张表的笛卡尔积:
- 从第一张表中选出第一条记录,和第二张表的所有记录组合;
- 然后从第一张表中取第二条记录,和第二张表中的所有记录组合;
- 不加过滤条件,得到的结果称为笛卡尔积。
因此,多表查询必须加上正确的连接条件。
2.2 显示雇员名、雇员工资以及所在部门的名字
数据来自 EMP 和 DEPT,需要联合查询:
sql
select EMP.ename, EMP.sal, DEPT.dname
from EMP, DEPT
where EMP.deptno = DEPT.deptno;
2.3 显示部门号为 10 的部门名、员工名和工资
sql
select ename, sal, dname
from EMP, DEPT
where EMP.deptno = DEPT.deptno
and EMP.deptno = 10;
2.4 显示各个员工的姓名、工资及工资级别
sql
select ename, sal, grade
from EMP, SALGRADE
where EMP.sal between losal and hisal;
3. 自连接
自连接是指在同一张表内进行连接查询。
案例:显示员工 FORD 的上级领导的编号和姓名(mgr 是员工领导的编号,对应 empno)。
使用子查询:
sql
select empno, ename
from emp
where emp.empno = (select mgr from emp where ename = 'FORD');
使用多表查询(自查询):
sql
select leader.empno, leader.ename
from emp leader, emp worker
where leader.empno = worker.mgr
and worker.ename = 'FORD';
这里给同一张表起了两个别名:leader 表示领导,worker 表示员工。因为要先做笛卡尔积,所以别名可以提前识别。
4. 子查询
子查询是指嵌入在其他 SQL 语句中的 select 语句,也叫嵌套查询。
4.1 单行子查询
返回一行记录的子查询。
显示 SMITH 同一部门的员工:
sql
select *
from EMP
where deptno = (select deptno from EMP where ename = 'SMITH');
4.2 多行子查询
返回多行记录的子查询,常用关键字有 in、all、any。
in 关键字:查询和 10 号部门工作岗位相同的雇员的名字、岗位、工资、部门号,但不包含 10 号部门自己:
sql
select ename, job, sal, deptno
from emp
where job in (select distinct job from emp where deptno = 10)
and deptno <> 10;
all 关键字:显示工资比部门 30 的所有员工工资都高的员工姓名、工资和部门号:
sql
select ename, sal, deptno
from EMP
where sal > all(select sal from EMP where deptno = 30);
any 关键字:显示工资比部门 30 的任意员工工资高的员工姓名、工资和部门号(包含自己部门的员工):
sql
select ename, sal, deptno
from EMP
where sal > any(select sal from EMP where deptno = 30);
4.3 多列子查询
单行子查询只返回单列单行;多行子查询返回单列多行;多列子查询则返回多个列数据。
案例:查询和 SMITH 的部门和岗位完全相同的所有雇员,不含 SMITH 本人:
sql
select ename
from EMP
where (deptno, job) = (select deptno, job from EMP where ename = 'SMITH')
and ename <> 'SMITH';
4.4 在 FROM 子句中使用子查询
子查询可以出现在 from 子句中,此时把一个子查询当作临时表使用。
案例 1:显示每个高于自己部门平均工资的员工姓名、部门、工资、平均工资。
sql
select ename, deptno, sal, format(asal, 2)
from EMP,
(select avg(sal) asal, deptno dt from EMP group by deptno) tmp
where EMP.sal > tmp.asal
and EMP.deptno = tmp.dt;
案例 2:查找每个部门工资最高的人的姓名、工资、部门、最高工资。
sql
select EMP.ename, EMP.sal, EMP.deptno, ms
from EMP,
(select max(sal) ms, deptno from EMP group by deptno) tmp
where EMP.deptno = tmp.deptno
and EMP.sal = tmp.ms;
案例 3:显示每个部门的信息(部门名、编号、地址)和人员数量。
方法 1:使用多表查询。
sql
select DEPT.dname, DEPT.deptno, DEPT.loc, count(*) '部门人数'
from EMP, DEPT
where EMP.deptno = DEPT.deptno
group by DEPT.deptno, DEPT.dname, DEPT.loc;
方法 2:使用子查询。
sql
-- 先对 EMP 表进行人员统计
select count(*), deptno from EMP group by deptno;
-- 将上面的表看作临时表
select DEPT.deptno, dname, mycnt, loc
from DEPT,
(select count(*) mycnt, deptno from EMP group by deptno) tmp
where DEPT.deptno = tmp.deptno;
5. 合并查询
在实际应用中,为了合并多个 select 的执行结果,可以使用集合操作符 union 和 union all。
5.1 UNION
union 用于取得两个结果集的并集,并自动去掉重复行。
案例:将工资大于 2500 或职位是 MANAGER 的人找出来。
sql
select ename, sal, job from EMP where sal > 2500
union
select ename, sal, job from EMP where job = 'MANAGER';
5.2 UNION ALL
union all 同样用于合并结果集,但不会去重。
sql
select ename, sal, job from EMP where sal > 2500
union all
select ename, sal, job from EMP where job = 'MANAGER';
使用 union 时需要注意:两个 select 的列数、列类型必须一致,否则会报错。