子查询
约 1220 字大约 4 分钟
布欧-Lewyon
2026-05-15
首页 › MySQL › 高级查询(在新窗口打开) › 子查询
子查询(Subquery)是嵌套在另一个查询中的查询,可以出现在 SELECT、FROM、WHERE、HAVING 等子句中。MySQL 8.0 对子查询优化较好,但并非所有子查询都比 JOIN 高效。
沿用 department / employee 表,并新增 orders 表。你可以在下面的运行框中直接查询:
CREATE TABLE department (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE employee (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
salary DECIMAL(10,2)
);
INSERT INTO department VALUES
(1, '技术部'), (2, '市场部'), (3, '人事部');
INSERT INTO employee VALUES
(1, '张三', 1, 15000), (2, '李四', 2, 12000),
(3, '王五', 1, 18000), (4, '赵六', 1, 22000),
(5, '孙七', 2, 11000), (6, '周八', 3, 9000),
(7, '吴九', NULL, NULL);
CREATE TABLE orders (
id INT PRIMARY KEY,
employee_id INT,
amount DECIMAL(10,2),
order_date DATE
);
INSERT INTO orders VALUES
(1, 1, 500, '2026-01-10'), (2, 1, 1200, '2026-02-15'),
(3, 2, 300, '2026-03-01'), (4, 3, 2500, '2026-01-20'),
(5, 4, 800, '2026-02-28'), (6, 5, 150, '2026-03-10'),
(7, 4, 3200, '2026-04-05');
标量子查询(Scalar Subquery)
返回单个值(一行一列),可放在 SELECT 或 WHERE 中:
-- 查询每位员工的姓名及其对应的部门名称(用子查询替代 JOIN)
SELECT
e.name,
(SELECT d.name FROM department d WHERE d.id = e.dept_id) AS dept_name
FROM employee e;-- 查询薪资高于平均水平的员工
SELECT name, salary
FROM employee
WHERE salary > (SELECT AVG(salary) FROM employee);行子查询(Row Subquery)
返回一行多列:
-- 找到薪资和部门都与张三相同的员工
SELECT name, dept_id, salary
FROM employee
WHERE (dept_id, salary) = (
SELECT dept_id, salary FROM employee WHERE name = '张三'
);
-- 如果只有张三本人,结果就是张三自己;若有其他人的部门+薪资与张三完全一致,也会列出EXISTS / NOT EXISTS
检查子查询是否有返回行,比 IN 在某些场景下效率更高:
-- 有订单的员工
SELECT e.name
FROM employee e
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.employee_id = e.id
);
-- 张三、李四、王五、赵六、孙七
-- 没有订单的员工
SELECT e.name
FROM employee e
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.employee_id = e.id
);
-- 周八、吴九
EXISTS是半连接,找到第一个匹配项即停止,比IN子查询全量计算更高效,尤其当子查询结果集很大时。
ANY / ALL
配合比较运算符使用:
-- ANY:大于子查询中的任意一个值
-- 找到薪资比技术部任一员工高的员工
SELECT name, salary
FROM employee
WHERE salary > ANY (
SELECT salary FROM employee WHERE dept_id = 1
);
-- 技术部薪资:15000, 18000, 22000
-- 只要超过 15000 即可,结果:张三(15000?)、王五(18000)、赵六(22000)
-- 注意:> ANY 等价于 > MIN()
-- ALL:大于子查询中的所有值
-- 找到薪资比技术部所有员工都高的员工
SELECT name, salary
FROM employee
WHERE salary > ALL (
SELECT salary FROM employee WHERE dept_id = 1
);
-- 技术部最高为 22000,无人超过
-- > ALL 等价于 > MAX()FROM 子句中的派生表(Derived Table)
子查询的结果当作临时表使用,必须起别名:
-- 各部门平均薪资,再从中找到平均值 > 12000 的
SELECT dept_id, avg_sal
FROM (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employee
GROUP BY dept_id
) AS dept_avg
WHERE avg_sal > 12000;LATERAL 派生表(MySQL 8.0.14+)
派生表中的子查询可以引用外层查询的列:
-- 每个部门薪资最高的员工
SELECT d.name AS dept_name, top.name, top.salary
FROM department d
LEFT JOIN LATERAL (
SELECT name, salary
FROM employee
WHERE dept_id = d.id
ORDER BY salary DESC
LIMIT 1
) AS top ON TRUE;
-- 技术部: 赵六 22000
-- 市场部: 李四 12000
-- 人事部: 周八 9000
-- 财务部: NULL NULL没有
LATERAL时,派生表无法感知外层d.id;有了它,每个部门各自查一次最高薪资,解决"分组 TOP-N"问题。
IN / NOT IN 子查询
-- 在有订单的员工
SELECT name FROM employee WHERE id IN (
SELECT employee_id FROM orders
);
-- 注意 NULL 问题
SELECT name FROM employee WHERE id NOT IN (
SELECT employee_id FROM orders WHERE employee_id IS NOT NULL
);
NOT IN当子查询结果中包含 NULL 时,整个条件返回 UNKNOWN(不返回任何行)。安全写法:NOT IN (SELECT ... WHERE col IS NOT NULL),或用NOT EXISTS替代。
子查询与 JOIN 的取舍
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 查询列中需要聚合值 | 标量子查询 | 简单直观 |
| 多表数据组合展示 | JOIN | 语义清晰,优化器优化空间大 |
| 检查存在性 | EXISTS | 半连接,短路效应 |
| 分组 TOP-N | LATERAL + 子查询 | 8.0.14+ 专属,比窗口函数更灵活 |
| 复杂多步计算 | CTE(下一节) | 可读性最佳 |
小结
- 标量子查询返回单值,行子查询返回单行多列。
EXISTS做存在性检查,比IN更高效(短路逻辑)。FROM子句中的派生表必须起别名;LATERAL派生表可引用外层列(8.0.14+)。NOT IN警惕 NULL 陷阱,优先用NOT EXISTS。- 子查询和 JOIN 可相互替代,具体选用视可读性和性能而定。
