MySQL: MySQL子查询与EXISTS详解
最后更新:2026-08-26
子查询是 SQL 的嵌套能力——一个查询的结果作为另一个查询的条件。
本课系统讲解子查询的各种形式和优化。
graph TB
A[子查询分类] --> B[标量子查询<br/>返回单值]
A --> C[列子查询<br/>返回一列]
A --> D[行子查询<br/>返回一行]
A --> E[表子查询<br/>返回表]
C --> C1[IN / NOT IN]
C --> C2[ANY / ALL]
E --> E1[FROM 派生表]
A --> F[EXISTS<br/>存在性检查]
A --> G[关联子查询<br/>引用外表]
1. 你将学到
- 标量子查询(返回单值)
- 列子查询(返回一列)
- 行子查询(返回一行)
- 表子查询(返回表)
- EXISTS/NOT EXISTS
2. 真实场景
(1) 痛点:无法一步到位
想查"工资高于平均工资的员工"——需要先算平均,再比较。
(2) 子查询的解法
SQL
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
3. 标量子查询
返回单行单列。
▶ 示例:标量子查询
SQL
-- 工资高于平均值的员工
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- 工资最高的员工
SELECT * FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees);
-- 最新订单的客户
SELECT * FROM customers
WHERE id = (SELECT customer_id FROM orders ORDER BY order_date DESC LIMIT 1);
4. 列子查询
返回单列多行。配合 IN/NOT IN/ANY/ALL 使用。
▶ 示例:IN 子查询
SQL
-- 有订单的客户
SELECT * FROM customers
WHERE id IN (SELECT DISTINCT customer_id FROM orders);
-- 没有订单的客户
SELECT * FROM customers
WHERE id NOT IN (SELECT DISTINCT customer_id FROM orders WHERE customer_id IS NOT NULL);
▶ 示例:ANY/ALL 子查询
SQL
-- 工资高于 Sales 部门任意一人的员工(比最低的高)
SELECT * FROM employees
WHERE salary > ANY (SELECT salary FROM employees WHERE department = 'Sales');
-- 工资高于 Sales 部门所有人的员工(比最高的还高)
SELECT * FROM employees
WHERE salary > ALL (SELECT salary FROM employees WHERE department = 'Sales');
5. EXISTS 子查询
检查子查询是否有结果返回。
▶ 示例:EXISTS
SQL
-- 有订单的客户
SELECT * FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
-- 没有订单的客户
SELECT * FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);
(1) EXISTS vs IN 对比
| 维度 | EXISTS | IN |
|---|---|---|
| 执行方式 | 对外表逐行检查子查询 | 先执行子查询,再匹配 |
| 适用场景 | 外表小,子查询大 | 外表大,子查询小 |
| NULL 处理 | 安全 | NOT IN 有 NULL 陷阱 |
| 性能 | 通常更好 | 子查询结果集小时更好 |
6. 表子查询(派生表)
子查询作为临时表。
▶ 示例:FROM 子查询
SQL
-- 每个部门工资最高的人
SELECT e.* FROM employees e
INNER JOIN (
SELECT department, MAX(salary) AS max_salary
FROM employees
GROUP BY department
) dept_max ON e.department = dept_max.department AND e.salary = dept_max.max_salary;
-- 客户订单统计
SELECT c.name, order_stats.total_orders, order_stats.total_amount
FROM customers c
INNER JOIN (
SELECT customer_id, COUNT(*) AS total_orders, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
) order_stats ON c.id = order_stats.customer_id;
7. 关联子查询
子查询引用外表的字段。
▶ 示例:关联子查询
SQL
-- 每个部门工资最高的员工
SELECT * FROM employees e1
WHERE salary = (
SELECT MAX(salary) FROM employees e2
WHERE e2.department = e1.department
);
-- 比同部门平均工资高的员工
SELECT * FROM employees e1
WHERE salary > (
SELECT AVG(salary) FROM employees e2
WHERE e2.department = e1.department
);
8. 子查询优化
| 策略 | 说明 |
|---|---|
| 用 JOIN 替代子查询 | MySQL 优化器通常能优化,但 JOIN 更直观 |
| 用 EXISTS 替代 IN | 数据量大时 EXISTS 通常更快 |
| 避免关联子查询 | 尽量改写为 JOIN |
| 子查询加索引 | 子查询中的连接/过滤字段加索引 |
❓ 常见问题
Q 子查询和 JOIN 哪个快?
A 通常 JOIN 更快(优化器更好优化)。但具体取决于数据分布,用 EXPLAIN 对比。
Q 子查询能嵌套多少层?
A MySQL 限制嵌套深度(通常 255 层),但实际建议不超过 3 层。
Q NOT IN 有 NULL 陷阱?
A 是的。
NOT IN (1, 2, NULL) 永远返回空。用 NOT EXISTS 替代。Q 子查询和 JOIN 哪个快?
A 取决于数据量和索引,通常 JOIN 更优。建议用 EXPLAIN 对比实际执行计划。
Q 子查询能嵌套几层?
A 理论上无限制,但超过 3 层可读性极差,建议改用 CTE(WITH 子句)。
📖 小节
- 标量子查询 返回单值,用
= / > / <比较 - 列子查询 返回多行,用
IN / NOT IN / ANY / ALL - EXISTS 检查子查询是否有结果,通常比 IN 更优
- 表子查询 作为派生表,在 FROM 中使用
- 关联子查询 引用外表字段,每行执行一次
- 优化原则:用 JOIN 替代子查询,用 EXISTS 替代 NOT IN
📝 作业
-
基础题(难度⭐):用子查询找出工资高于平均工资的员工。
-
进阶题(难度⭐⭐):用 EXISTS 找出没有下过订单的客户。
-
挑战题(难度⭐⭐⭐):用关联子查询找出每个部门工资最高的员工。