大家好,在 MySQL 日常开发里,单表查询 只能处理最简单的数据需求,真正的业务场景几乎都要用到复合查询------ 也就是多表关联、嵌套查询、自连接、结果合并这类高级查询。
今天这篇文章,我就带着大家把复合查询从基础到实战彻底讲透,每一个知识点都配案例 + 解释,小白也能轻松学会。
一、先回顾:单表基础查询(温故知新)
复合查询是单表查询的进阶,我们先用几个经典案例快速过一遍重点语法。
1. 多条件筛选
查询工资高于 500 或岗位是 MANAGER,且姓名以 J 开头的员工:
sql
SELECT * FROM EMP
WHERE (sal>500 OR job='MANAGER')
AND ename LIKE 'J%';
2. 多字段排序
按部门号升序、同部门内工资降序:
sql
SELECT * FROM EMP ORDER BY deptno asc, sal DESC;
3. 计算年薪并排序
奖金为空时用 IFNULL 转 0,避免计算错误:
sql
SELECT ename, sal*12+IFNULL(comm,0) AS '年薪'
FROM EMP
ORDER BY 年薪 DESC;
4. 聚合函数搭配子查询
- 查工资最高的员工:
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);
5. 分组统计 + 分组后过滤
- 每个部门平均工资(保留两位小数)、最高工资:
sql
SELECT deptno, FORMAT(AVG(sal), 2), MAX(sal)
FROM EMP
GROUP BY deptno;
-
这里的FORMAT 是 MySQL 里专门用来「格式化数字 / 日期」的函数,最常用作用是:把数字保留指定位小数、加千分位分隔符。标准格式:FORMAT(数字, 保留小数位数)。
-
平均工资低于 2000 的部门:
sql
SELECT deptno, AVG(sal) AS avg_sal
FROM EMP
GROUP BY deptno
HAVING avg_sal < 2000;
二、多表查询:跨表取数的核心
实际开发中,数据分散在多张表里,必须用多表连接才能拿到完整信息。
本文用经典 3 张表演示:
EMP:员工表(员工号、姓名、岗位、工资、部门号...)DEPT:部门表(部门号、部门名、位置...)SALGRADE:工资等级表(等级、最低工资、最高工资)
1. 什么是笛卡尔积
不加连接条件直接查多张表,会出现全组合 ,数据量爆炸,绝对不能用。

sql
-- 错误示例:产生笛卡尔积
SELECT * FROM EMP, DEPT;
2. 正确多表查询(内连接)
必须加上关联条件(通常是外键 = 主键)。
案例 1:员工名、工资、所在部门名
sql
SELECT EMP.ename, EMP.sal, DEPT.dname
FROM EMP, DEPT
WHERE EMP.deptno = DEPT.deptno;
案例 2:只看 10 号部门的员工与部门名
sql
SELECT ename, sal, dname
FROM EMP, DEPT
WHERE EMP.deptno = DEPT.deptno
AND DEPT.deptno = 10;
案例 3:员工姓名、工资、工资等级
sql
SELECT ename, sal, grade
FROM EMP, SALGRADE
WHERE EMP.sal BETWEEN losal AND hisal;
三、自连接:一张表自己连自己
自连接 :同一张表起两个别名,当成两张表用。典型场景:员工与领导关系 (员工表的 mgr 指向领导的 empno)。
案例:查员工 FORD 的上级编号与姓名
- 方式 1:子查询
sql
SELECT empno, ename
FROM emp
WHERE empno = (SELECT mgr FROM emp WHERE ename='FORD');
- 方式 2:自连接(更优雅)
sql
SELECT leader.empno, leader.ename
FROM emp leader, emp worker
WHERE leader.empno = worker.mgr
AND worker.ename='FORD';
要点:给表起别名,区分 "领导表"leader 和 "员工表"worker。
四、子查询(嵌套查询):复合查询灵魂
子查询 :把一个 SELECT 嵌套在另一个 SQL 里,先执行内层,再执行外层。
1. 单行子查询(返回 1 行 1 列)
用于 = > < >= <= 这类单值比较。
案例:和 SMITH 同一部门的员工
sql
SELECT * FROM EMP
WHERE deptno = (SELECT deptno FROM EMP WHERE ename='SMITH');
2. 多行子查询(返回多行 1 列)
必须搭配 IN / ANY / ALL 使用。
① 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);
3. 多列子查询(返回多列)
同时匹配多个字段 ,用 (字段1, 字段2) = (子查询列1, 列2)。
案例:和 SMITH 部门、岗位完全相同的人(排除 SMITH):
sql
SELECT ename FROM EMP
WHERE (deptno, job) = (SELECT deptno, job FROM EMP WHERE ename='SMITH')
AND ename <> 'SMITH';
4. 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:部门信息 + 部门人数
sql
SELECT DEPT.deptno, dname, mycnt, loc
FROM DEPT,
(SELECT COUNT(*) mycnt, deptno FROM EMP GROUP BY deptno) tmp
WHERE DEPT.deptno = tmp.deptno;
五、合并查询:UNION 与 UNION ALL
把多个 SELECT 结果纵向拼接,要求:
- 列数相同
- 对应列类型兼容
- 列名以第一个 SELECT 为准
1. UNION:合并并自动去重
sql
SELECT ename, sal, job FROM EMP WHERE sal>2500
UNION
SELECT ename, sal, job FROM EMP WHERE job='MANAGER';
2. UNION ALL:直接合并,不去重
性能比 UNION 高很多,确定无重复时优先用它。
sql
SELECT ename, sal, job FROM EMP WHERE sal>2500
UNION ALL
SELECT ename, sal, job FROM EMP WHERE job='MANAGER';
对比速记
表格
| 关键字 | 是否去重 | 性能 | 适用场景 |
|---|---|---|---|
| UNION | 是 | 较低 | 需去重 |
| UNION ALL | 否 | 高 | 允许重复 / 确定无重复 |
六、实战 OJ 常考题型(练手必备)
- 查找所有员工入职时候的薪水情况_牛客题霸_牛客网
- 获取所有非manager的员工emp_no_牛客题霸_牛客网
- 获取所有员工当前的manager_牛客题霸_牛客网
- 【MySQL牛客】11.获取所有员工当前的manager-CSDN博客
这些题在牛客网非常高频,练完基本面试稳了。
七、复合查询核心总结
- 多表查询一定要加连接条件,避免笛卡尔积
- 自连接 = 同表起别名,处理层级关系
- 子查询分:单行 / 多行 / 多列 / FROM 子查询
IN / ANY / ALL专门处理多行子查询UNION去重,UNION ALL性能更高- 分组后过滤用
HAVING,不是WHERE
八、学习建议
- 先把本文案例手敲一遍
- 用
EXPLAIN看执行计划,理解查询原理 - 多刷牛客 / LeetCode SQL 专题,强化手感
复合查询是 MySQL 最核心、面试最高频的知识点,吃透它,你的 SQL 水平会直接上一个台阶。