SQL子查询是指嵌套在SELECT、INSERT、UPDATE、DELETE语句或者其他子查询中的查询语句,它可以返回单个值、一行数据或者多行数据集,能够帮助我们完成很多单条查询无法实现的复杂逻辑。合理使用子查询可以简化SQL代码的编写,但不合理的子查询往往会导致查询性能急剧下降。

SQL子查询的常见使用方法
标量子查询
标量子查询返回单个值,通常用在SELECT子句或者WHERE条件的比较场景中。比如我们需要查询所有薪资高于公司平均薪资的员工信息,就可以使用标量子查询计算平均薪资。
-- 查询薪资高于公司平均薪资的员工
SELECT emp_id, emp_name, salary
FROM employee
WHERE salary > (
SELECT AVG(salary)
FROM employee
);
行子查询
行子查询返回一行多列的结果,一般和行比较操作符配合使用。例如查询和员工ID为1001的员工部门、职位都相同的其他员工信息。
-- 查询和1001员工部门、职位相同的其他员工
SELECT emp_id, emp_name, dept_id, job_title
FROM employee
WHERE (dept_id, job_title) = (
SELECT dept_id, job_title
FROM employee
WHERE emp_id = 1001
) AND emp_id != 1001;
表子查询
表子查询返回多行多列的结果集,通常用在FROM子句中作为临时表使用,也可以用在WHERE子句的IN、EXISTS条件中。比如查询每个部门薪资最高的员工信息,就可以先通过表子查询得到每个部门的最高薪资,再关联员工表匹配数据。
-- 查询每个部门薪资最高的员工
SELECT e.emp_id, e.emp_name, e.dept_id, e.salary
FROM employee e
INNER JOIN (
SELECT dept_id, MAX(salary) AS max_salary
FROM employee
GROUP BY dept_id
) d ON e.dept_id = d.dept_id AND e.salary = d.max_salary;
EXISTS子查询
EXISTS子查询用于判断子查询是否返回结果,只要子查询返回至少一行数据,EXISTS的结果就为真。它通常用在关联查询的筛选场景中,比如查询有下属的管理人员信息。
-- 查询有下属的管理人员
SELECT m.emp_id, m.emp_name
FROM employee m
WHERE EXISTS (
SELECT 1
FROM employee e
WHERE e.manager_id = m.emp_id
);
SQL子查询的优化策略
优先使用JOIN代替子查询
很多情况下,子查询可以被等价的JOIN查询替代,而数据库对JOIN的优化通常比子查询更成熟。比如上面的标量子查询示例,如果子查询的结果集较大,就可以改写为JOIN形式提升性能。
-- 用JOIN改写高于平均薪资的查询 SELECT e.emp_id, e.emp_name, e.salary FROM employee e CROSS JOIN (SELECT AVG(salary) AS avg_salary FROM employee) t WHERE e.salary > t.avg_salary;
减少子查询的嵌套层级
子查询嵌套层级越多,数据库优化器生成执行计划的难度就越大,执行效率也会越低。如果业务允许,尽量将多层嵌套的子查询拆分成多个简单的查询,或者使用临时表存储中间结果。
为子查询关联字段添加索引
如果子查询中涉及到关联条件或者筛选条件的字段,一定要确保这些字段上有合适的索引。比如EXISTS子查询中的关联字段manager_id,如果没有索引,全表扫描的开销会非常大。
避免在子查询中使用SELECT *
子查询中如果使用SELECT *,会返回所有字段,增加数据传输和内存开销,尤其是子查询作为临时表使用时,多余的字段会拖慢整体查询速度。应该只查询需要的字段。
合理使用EXISTS代替IN
当子查询返回的结果集较大时,使用IN子查询可能会导致性能问题,因为IN需要先执行子查询得到所有结果,再进行匹配。而EXISTS是逐行判断,只要匹配到就返回,适合子查询结果集大的场景。
-- 不推荐:IN子查询结果集大时性能差
SELECT emp_id, emp_name
FROM employee
WHERE dept_id IN (
SELECT dept_id
FROM department
WHERE dept_location = '北京'
);
-- 推荐:用EXISTS改写
SELECT e.emp_id, e.emp_name
FROM employee e
WHERE EXISTS (
SELECT 1
FROM department d
WHERE d.dept_id = e.dept_id AND d.dept_location = '北京'
);
子查询优化注意事项
不同的数据库对子查询的优化策略存在差异,比如MySQL和Oracle在处理子查询时的执行计划生成逻辑不同,优化时还需要结合具体使用的数据库版本和特性调整。另外,写完SQL后可以通过EXPLAIN命令查看执行计划,确认子查询是否被正确优化,比如是否出现了全表扫描、临时表使用过多等问题,再针对性调整查询逻辑。