MySQL: MySQL子查询与EXISTS详解

最后更新:2026-08-26

子查询是 SQL 的嵌套能力——一个查询的结果作为另一个查询的条件。

本课系统讲解子查询的各种形式和优化。

100%
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. 你将学到


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 子句)。

📖 小节


📝 作业

  1. 基础题(难度⭐):用子查询找出工资高于平均工资的员工。

  2. 进阶题(难度⭐⭐):用 EXISTS 找出没有下过订单的客户。

  3. 挑战题(难度⭐⭐⭐):用关联子查询找出每个部门工资最高的员工。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏