数据库系列5——子查询:把查询拆成“中间结果”的工程思维
数据库系列5——子查询:把查询拆成“中间结果”的工程思维
本文是【数据库工程系列】的一部分。
上一篇我们介绍了高级查询:排序 / 分页 / 聚合 / 分组 / 过滤一次讲透
如果你已经能写复杂查询,但在遇到:
- 查询嵌套
- 统计结果当条件
- IN / ANY / ALL 分不清
那这一篇,解决的是一个“思维问题”,而不是语法问题。
0. 开篇:为什么子查询不是高级语法,而是思维方式
很多人学子查询时,会陷入两个误区:
- 记语法形式:
IN / ANY / ALL - 背题型:单行单列、多行单列、多行多列
但在真实工程里,子查询解决的不是“语法问题”,而是“计算顺序问题”。
子查询的本质只有一句话:
先算一个中间结果,再把这个结果当条件继续判断。
只要你能判断:
这个中间结果长什么样,子查询就不再难。
1. 数据准备:employee 员工表
以下是课堂使用的 EMP 表结构与数据(示例可直接运行):
CREATE TABLE EMP(
EMPNO INT,
ENAME VARCHAR(10),
JOB VARCHAR(9),
MGR INT,
HIREDATE DATE,
SAL DOUBLE,
COMM DOUBLE,
DEPTNO INT
);
INSERT INTO EMP VALUES(7369,'SMITH','CLERK',7902,'1980-12-17',800,NULL,20);
INSERT INTO EMP VALUES(7499,'ALLEN','SALESMAN',7698,'1981-02-20',1600,300,30);
INSERT INTO EMP VALUES(7521,'WARD','SALESMAN',7698,'1981-02-22',1250,500,30);
INSERT INTO EMP VALUES(7566,'JONES','MANAGER',7839,'1981-04-02',2975,NULL,20);
INSERT INTO EMP VALUES(7654,'MARTIN','SALESMAN',7698,'1981-09-28',1250,1400,30);
INSERT INTO EMP VALUES(7698,'BLAKE','MANAGER',7839,'1981-05-01',2850,NULL,30);
INSERT INTO EMP VALUES(7782,'CLARK','MANAGER',7839,'1981-06-09',2450,NULL,10);
INSERT INTO EMP VALUES(7788,'SCOTT','ANALYST',7566,'1987-07-03',3000,NULL,20);
INSERT INTO EMP VALUES(7839,'KING','PRESIDENT',NULL,'1981-11-17',5000,NULL,10);
INSERT INTO EMP VALUES(7844,'TURNER','SALESMAN',7698,'1981-09-08',1500,0,30);
INSERT INTO EMP VALUES(7876,'ADAMS','CLERK',7788,'1987-07-13',1100,NULL,20);
INSERT INTO EMP VALUES(7900,'JAMES','CLERK',7698,'1981-12-03',950,NULL,30);
INSERT INTO EMP VALUES(7902,'FORD','ANALYST',7566,'1981-12-03',3000,NULL,20);
INSERT INTO EMP VALUES(7934,'MILLER','CLERK',7782,'1981-01-23',1300,NULL,10);
2. 单行单列子查询:把一个值当条件用
典型需求
查询:薪资高于公司平均薪资的员工
SQL 示例
SELECT EMPNO, ENAME, SAL
FROM EMP
WHERE SAL > (
SELECT AVG(SAL)
FROM EMP
);
工程解释
- 子查询先执行
- 计算出 一个值:公司平均工资
- 外层查询把这个值当作一个普通常量
关键判断点
当子查询返回 1 行 1 列时,它的角色就是“一个值”。
工程中你可以把它理解为:
SAL > 公司平均薪资
3. 多行单列子查询:把一组值交给 ANY / ALL / IN
典型需求
查询:薪资高于任一技术部员工的员工信息
SQL 示例(ANY)
SELECT EMPNO, ENAME, SAL
FROM EMP
WHERE SAL > ANY (
SELECT SAL
FROM EMP
WHERE DEPTNO = 20
);
工程解释
子查询返回的是:
- 多行
- 单列(SAL)
本质是一组数值集合。
ANY / ALL / IN 的工程级区别
假设子查询返回的集合是:
[800, 1100, 2975, 3000]
> ANY
→ 只要 大于最小值即可> ALL
→ 必须 大于最大值
-- 高于所有技术部员工
WHERE SAL > ALL (
SELECT SAL
FROM EMP
WHERE DEPTNO = 20
);
IN
→ 等于其中某一个
-- 薪资等于技术部任一员工
WHERE SAL IN (
SELECT SAL
FROM EMP
WHERE DEPTNO = 20
);
关键结论
多行单列子查询,本质是“集合判断”,
外层必须使用集合语义(IN / ANY / ALL)来接。
4. 多行多列子查询:用行值比较匹配结构
典型需求
查询:每个部门中,薪资等于该部门最高薪资的员工
SQL 示例
SELECT EMPNO, ENAME, DEPTNO, SAL
FROM EMP
WHERE (DEPTNO, SAL) IN (
SELECT DEPTNO, MAX(SAL)
FROM EMP
GROUP BY DEPTNO
);
工程解释(这是子查询最关键的一步)
子查询返回的是:
| DEPTNO | MAX(SAL) |
|---|---|
这是一个 多行多列的结构结果。
外层查询必须:
- 用同样结构
(DEPTNO, SAL) - 才能做 行值比较
关键结论
子查询返回的是“结构”,
外层就必须用“结构”去匹配,而不是单个字段。
这一步,本质是在做:
(部门, 工资) ∈ (部门, 最高工资)
5. 子查询的“三问判断法”
遇到任何子查询,不要急着写 SQL,先问自己三件事:
1️⃣ 子查询返回几行?
2️⃣ 子查询返回几列?
3️⃣ 外层 WHERE 应该用什么结构才能接住?
只要这三问清楚,写法自然就出来了。
6. 本篇小结与速查表
子查询速查表
| 子查询类型 | 返回形态 | 外层写法 |
|---|---|---|
| 单行单列 | 1 行 1 列 | = > < |
| 多行单列 | 多行 1 列 | IN / ANY / ALL |
| 多行多列 | 多行多列 | (a, b) IN (...) |
一句话总结
子查询不是嵌套 SQL,
而是:先算一个中间结果,再用这个结果参与判断。
如果你能先判断“结果形态”,
子查询就只是顺手写出来的自然结果。
到这里,你已经掌握了一种非常重要的工程能力:
- 先判断子查询“返回形态”
- 再决定外层如何接住结果
- 把复杂问题拆成可解释的中间结果
这意味着:
你的 SQL 已经具备“可组合性”了。
但到目前为止,我们一直在“读数据”。
接下来,是数据库里风险最高的一类操作:
👉 下一篇:《数据库系列6——增删改操作:数据库是如何被“安全写入”的》









