子查询,也被称为嵌套查询或内层查询,它本质上就是嵌套在另一个 SQL 查询内部的查询语句。它就像编程中的函数调用,是 SQL 中实现逻辑复用的重要模块,能将复杂的业务问题分解为清晰的步骤。
子查询可以出现在 SELECT、FROM、WHERE、HAVING、EXISTS 等子句中,用于提供数据、过滤条件或充当临时数据源。
一、子查询的基本语法
-- 主查询(外部查询)
SELECT column_list
FROM table1 t1
WHERE condition
AND column1 operator (
-- 子查询(内部查询)
SELECT column_a
FROM table2 t2
WHERE t2.col = t1.col -- 可选:关联条件(相关子查询)
);
| 关键字 |
说明 |
SELECT ... FROM ... WHERE |
外部主查询 |
(SELECT ...) |
子查询,必须用括号包围 |
operator |
比较运算符:=, >, <, IN, EXISTS, ANY, ALL 等 |
t2.col = t1.col |
若存在此引用,则为“相关子查询”(Correlated Subquery) |
核心要点:子查询的生命周期依附于外层查询,通常不能独立执行。当存在多层嵌套时,数据库的执行顺序遵循“由内到外”的原则。
二、子查询的两种分类维度
SQL 子查询可以从两个交叉维度进行分类:
| 分类方式 |
类型 |
| 按结果形式(结果形态)分类 |
① 标量子查询 ② 行子查询 ③ 列子查询 ④ 表子查询 ⑤ EXISTS子查询 |
| 按执行依赖关系(执行模式)分类 |
① 相关(关联)子查询(Correlated Subquery) ② 非相关子查询(Non-Correlated Subquery) |
一个子查询可以同时属于两种分类,例如,它可能既是“标量子查询”又是“相关子查询”:
SELECT name, (SELECT AVG(salary) FROM emp WHERE dept = e.dept) FROM emp e;
三、标量子查询(Scalar Subquery)
标量子查询返回单个值(一行一列),通常用于与单值进行比较或作为计算表达式的一部分。
1. 语法与要点
SELECT
col1,
(SELECT expr FROM table2 WHERE condition) AS computed_value
FROM table1 t1
WHERE col2 > (SELECT AVG(col3) FROM table3);
- 结果唯一性:必须返回 0 或 1 行,否则数据库会报错(如 MySQL 的
Subquery returns more than 1 row)。
- 使用位置:可用于
SELECT、WHERE、HAVING 子句。
- 运算符:支持
=、>、<、>=、<=、<>。
2. 示例:查询工资高于公司平均工资的员工
SELECT
name, salary
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees -- 这是一个非相关子查询
);
执行过程:
- 执行子查询
SELECT AVG(salary) FROM employees,得到平均值(假设为 75000)。
- 执行主查询
SELECT name, salary FROM employees WHERE salary > 75000。
- 返回最终结果。
3. 优化与替代方案
| 优化方法 |
说明 |
| 改写为 JOIN |
将标量子查询转为 JOIN + WHERE,避免重复执行 |
| 添加索引 |
在子查询的 WHERE 条件列(如 salary)上建立索引 |
| 使用窗口函数替代 |
如 AVG(salary) OVER(),可避免子查询,性能更优 |
优化示例(使用窗口函数):
SELECT name, salary
FROM (
SELECT name, salary, AVG(salary) OVER() AS avg_sal
FROM employees
) t
WHERE salary > avg_sal;
四、行子查询(Row Subquery)
行子查询返回一行多列的数据,用于多列同时匹配的场景。
1. 语法与要点
SELECT *
FROM table1
WHERE (col1, col2) = (
SELECT col_a, col_b
FROM table2
WHERE condition
);
- 列数一致:左右两边的列数必须相等。
- 支持运算符:
=, <>, IN, NOT IN。
- 注意:像
>、< 这类比较符在大部分数据库中不支持行间比较,除非使用特定数据库的扩展功能(如 PostgreSQL),为避免兼容性问题,建议慎用。
2. 示例:查找与 Alice 工资和部门相同的员工
SELECT name, salary, dept
FROM employees
WHERE (salary, dept) = (
SELECT salary, dept
FROM employees
WHERE name = 'Alice'
);
优化建议:可改写为 JOIN,性能通常更好,也便于数据库优化器生成更佳的执行计划。
SELECT e1.name, e1.salary, e1.dept
FROM employees e1
JOIN employees e2 ON e1.salary = e2.salary AND e1.dept = e2.dept
WHERE e2.name = 'Alice';
五、列子查询(Column Subquery)
列子查询返回一列多行的数据,主要用于集合成员判断。
1. 语法与要点
SELECT *
FROM table1
WHERE col1 IN (
SELECT col_a
FROM table2
WHERE condition
);
- 支持运算符:
IN, NOT IN, ANY, ALL, SOME。
= ANY 等价于 IN
<> ALL 等价于 NOT IN
- 陷阱:
NOT IN 对 NULL 值非常敏感。如果子查询结果集中包含 NULL,则整个 NOT IN 表达式的结果将为 UNKNOWN,导致查询无结果。因此,强烈建议用 NOT EXISTS 替代 NOT IN。
2. 示例:查找财务部和销售部的员工
SELECT name, dept
FROM employees
WHERE dept IN (
SELECT dept_name
FROM departments
WHERE dept_type IN ('Finance', 'Sales')
);
优化建议:对于大数据集,使用 JOIN 替代 IN 往往能获得更好的性能。
SELECT e.name, e.dept
FROM employees e
JOIN departments d ON e.dept = d.dept_name
WHERE d.dept_type IN ('Finance', 'Sales');
六、表子查询(Table Subquery / Derived Table)
表子查询返回多行多列的结果集,作为临时表使用,必须使用别名。
1. 语法与要点
SELECT t.col1, t.col2
FROM (
SELECT col_a, col_b
FROM table1
WHERE condition
) AS t -- 别名是必须的
WHERE t.col1 > value;
- 别名必需:必须使用
AS alias 为子查询结果命名,否则语法错误。
- 使用位置:通常位于
FROM 子句中。
- 非关联性:它通常是非相关子查询,无法直接引用外部查询的列。
- 复杂度控制:虽然可以多层嵌套,但应控制深度以保证可读性。
2. 示例:统计各部门平均工资并筛选出高于60000的部门
SELECT dept, avg_salary
FROM (
SELECT dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
) AS dept_avg
WHERE avg_salary > 60000;
优化与可读性建议:对于复杂的多层查询,使用 CTE (公共表表达式) 可以极大地提升代码清晰度和可维护性。
WITH dept_avg AS (
SELECT dept, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
)
SELECT * FROM dept_avg WHERE avg_salary > 60000;
七、EXISTS 子查询(Existential Subquery)
EXISTS 子查询用于判断是否存在满足条件的行,只返回布尔值 TRUE 或 FALSE。
1. 语法与要点
SELECT *
FROM table1 t1
WHERE EXISTS (
SELECT 1 -- 推荐使用 SELECT 1 或 SELECT NULL,无需查询具体列
FROM table2 t2
WHERE t2.ref = t1.id -- 通常是关联条件
);
- 只关心存在性:子查询中
SELECT 什么并不重要,SELECT 1 是高效且清晰的写法。
- 通常为关联子查询:几乎总是通过条件与外部查询关联。
- 短路求值:一旦找到一行匹配数据,立即返回
TRUE,停止搜索,这对性能有利。
NOT EXISTS 的妙用:常用于查找“没有...”的集合差集,例如“没有订单的客户”。
2. 示例:查找有订单的客户
SELECT customer_name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);
性能选择:
- 外表小,内表大:
EXISTS 配合内表索引,性能通常优于 IN。
- 内表小,外表大:
IN(子查询结果可被缓存)可能更快。
- 在涉及高并发与复杂系统设计的查询中,理解
EXISTS 与 JOIN 的执行计划差异至关重要。
相关子查询是指子查询的执行依赖于外部查询的当前行。它会为外部查询的每一行都执行一次,因此性能开销较大(复杂度可达 O(n×m)),但其逻辑表达能力非常强。
1. 示例与执行过程
-- 为每个员工计算其所在部门的平均工资
SELECT
name,
salary,
(SELECT AVG(salary) FROM employees e2 WHERE e2.dept = e1.dept) AS dept_avg
FROM employees e1;
执行过程:
- 主查询读取
employees 表第一行(如:Alice, HR, 70000)。
- 执行子查询:
SELECT AVG(salary) FROM employees WHERE dept = ‘HR’,得到结果 60000。
- 输出该行,并附上
dept_avg = 60000。
- 移动到下一行(如:Bob, Engineering, 80000),子查询变为
WHERE dept = ‘Engineering’,重复此过程。
2. 各类型子查询的关联性分析
| 类型 |
是否可能是关联子查询? |
说明 |
| 标量子查询 |
是 |
常见于 SELECT 或 WHERE 中,依赖外部列进行计算。 |
| 行子查询 |
是 |
用于 (col1, col2) = (...) 的多列匹配,条件可依赖外部。 |
| 列子查询 |
是 |
在 IN、ANY、ALL 中使用,条件可依赖外部。 |
| 表子查询 |
通常不是 |
位于 FROM 子句,必须先独立执行生成临时表,一般不能引用外部列。 |
| EXISTS 子查询 |
几乎总是 |
这是最典型的关联子查询应用场景。 |
结论:除了 FROM 子句中的表子查询外,其他所有类型的子查询(标量、行、列、EXISTS)都可能是关联子查询。
九、性能优化核心建议
什么时候该用子查询?又该如何选择类型呢?下图提供了一个清晰的决策路径:

此外,遵循以下优化原则可以大幅提升查询效率:
| 场景 |
推荐写法 |
原因 |
| 简单关联过滤 |
JOIN |
优化器更容易处理,执行计划更优。 |
| 存在性判断 |
EXISTS |
利用短路求值,尤其当子查询表大时性能好。 |
| 集合成员判断 |
IN(集合小)或 JOIN(集合大) |
避免 NOT IN 的 NULL 陷阱,JOIN 更可控。 |
| 聚合计算 |
窗口函数 OVER() |
一次扫描完成计算,彻底避免低效的相关子查询。 |
| 复杂多步查询 |
CTE(公用表表达式) |
提升可读性、可维护性,便于分步调试。 |
| 结果被多次引用 |
物化视图/临时表 |
减少重复计算的开销。 |
核心准则:凡能用 JOIN 或 CTE 清晰表达的,尽量不使用子查询。现代数据库优化器虽然能对简单子查询进行“解嵌套”优化,但对于复杂或关联子查询,手动重构为 JOIN/CTE 通常能带来更稳定、更高效的执行。
通用性能排序(仅供参考,实际取决于数据和索引):JOIN ≈ CTE > EXISTS > IN > 相关子查询 > 多层嵌套子查询。
掌握子查询的分类、特性与优化技巧,是编写高效、清晰 SQL 语句的关键。希望本文的梳理能帮助你在实际开发中游刃有余。更多数据库实战技巧与深度讨论,欢迎访问云栈社区与广大开发者交流。