欢迎光临
我们一直在努力

数据库系列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
);

工程解释(这是子查询最关键的一步)

子查询返回的是:

DEPTNOMAX(SAL)

这是一个 多行多列的结构结果。

外层查询必须:

  • 用同样结构 (DEPTNO, SAL)
  • 才能做 行值比较

关键结论

子查询返回的是“结构”, 外层就必须用“结构”去匹配,而不是单个字段。

这一步,本质是在做:

(部门, 工资) ∈ (部门, 最高工资)


5. 子查询的“三问判断法”

遇到任何子查询,不要急着写 SQL,先问自己三件事:

1️⃣ 子查询返回几行? 2️⃣ 子查询返回几列? 3️⃣ 外层 WHERE 应该用什么结构才能接住?

只要这三问清楚,写法自然就出来了。


6. 本篇小结与速查表

子查询速查表

子查询类型返回形态外层写法
单行单列 1 行 1 列 = > <
多行单列 多行 1 列 IN / ANY / ALL
多行多列 多行多列 (a, b) IN (…)

一句话总结

子查询不是嵌套 SQL, 而是:先算一个中间结果,再用这个结果参与判断。

如果你能先判断“结果形态”, 子查询就只是顺手写出来的自然结果。 到这里,你已经掌握了一种非常重要的工程能力:

  • 先判断子查询“返回形态”
  • 再决定外层如何接住结果
  • 把复杂问题拆成可解释的中间结果

这意味着: 你的 SQL 已经具备“可组合性”了。

但到目前为止,我们一直在“读数据”。

接下来,是数据库里风险最高的一类操作:

👉 下一篇:《数据库系列6——增删改操作:数据库是如何被“安全写入”的》

赞(0)
未经允许不得转载:171主机测评 » 数据库系列5——子查询:把查询拆成“中间结果”的工程思维
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址