SQL子查询指的是嵌套在SELECT、INSERT、UPDATE、DELETE等SQL语句中的查询语句,它可以作为外层查询的条件、数据源或者计算结果使用,能够帮助我们实现复杂的查询需求,减少多步查询的操作成本。

SQL子查询的常见类型与写法
1. 标量子查询
标量子查询返回的结果是单个值,通常用在WHERE子句或者SELECT列表中作为计算字段。比如我们需要查询工资高于部门平均工资的员工信息,就可以用标量子查询计算部门平均工资。
-- 查询工资高于所在部门平均工资的员工
SELECT e.emp_id, e.emp_name, e.salary, e.dept_id
FROM employee e
WHERE e.salary > (
-- 标量子查询,返回当前部门的平均工资
SELECT AVG(salary)
FROM employee
WHERE dept_id = e.dept_id
);
2. 列子查询
列子查询返回的是单列多行的数据集,通常配合IN、ANY、ALL等操作符使用。比如查询所有在销售部或者研发部的员工信息,就可以先通过子查询获取这两个部门的ID。
-- 查询销售部和研发部的员工
SELECT emp_id, emp_name, dept_id
FROM employee
WHERE dept_id IN (
-- 列子查询,返回销售部和研发部的部门ID
SELECT dept_id
FROM department
WHERE dept_name IN ('销售部', '研发部')
);
3. 表子查询
表子查询返回的是多行多列的数据集,通常作为外层查询的数据源,放在FROM子句后面,需要给子查询起一个别名。比如查询每个部门工资最高的员工,就可以先通过子查询按部门分组获取最高工资,再关联员工表。
-- 查询每个部门工资最高的员工
SELECT e.emp_id, e.emp_name, e.salary, e.dept_id
FROM employee e
INNER JOIN (
-- 表子查询,获取每个部门的最高工资
SELECT dept_id, MAX(salary) AS max_salary
FROM employee
GROUP BY dept_id
) t ON e.dept_id = t.dept_id AND e.salary = t.max_salary;
4. 相关子查询与不相关子查询
不相关子查询指的是子查询的执行不依赖外层查询的字段,子查询可以独立执行,比如上面的列子查询示例就是典型的不相关子查询。相关子查询则依赖外层查询的字段,子查询不能独立执行,比如第一个标量子查询的示例就是相关子查询,子查询中的dept_id = e.dept_id依赖外层查询的员工表部门ID。
SQL子查询的执行流程解析
1. 不相关子查询的执行流程
不相关子查询的执行逻辑比较简单,数据库会先执行子查询,得到子查询的结果集,再将这个结果集作为外层查询的输入条件或者数据源,执行外层查询。整个过程子查询只会执行一次,执行完成后结果会被缓存,外层查询多次引用子查询结果时不会重复执行子查询。
以之前的列子查询为例,执行步骤为:
- 第一步:执行子查询
SELECT dept_id FROM department WHERE dept_name IN ('销售部', '研发部'),得到部门ID列表,比如(1, 3)。 - 第二步:将外层查询的
dept_id与子查询返回的ID列表做匹配,筛选出符合条件的员工记录。
2. 相关子查询的执行流程
相关子查询的执行逻辑和不相关子查询不同,由于子查询依赖外层查询的字段,所以子查询不能独立执行。数据库会先遍历外层查询的每一条记录,将当前记录的对应字段值传递给子查询,执行子查询得到结果,再判断外层查询的当前记录是否符合条件,符合则保留,不符合则丢弃,然后处理下一条外层记录,重复这个过程直到外层所有记录处理完成。
以标量子查询查询高于部门平均工资的员工为例,执行步骤为:
- 第一步:从员工表中取出第一条员工记录,获取该员工的
dept_id值,比如是2。 - 第二步:执行子查询
SELECT AVG(salary) FROM employee WHERE dept_id = 2,得到部门2的平均工资。 - 第三步:比较当前员工的工资和子查询返回的平均工资,如果员工工资更高,则保留该员工记录,否则丢弃。
- 第四步:处理员工表的下一条记录,重复上面的步骤,直到所有员工记录处理完成。
3. 子查询的优化执行逻辑
现代数据库优化器会对子查询做很多优化,比如将子查询转换为连接查询,减少相关子查询的重复执行次数。比如上面的相关子查询示例,优化器可能会将其重写为表连接的形式,一次性获取所有部门的平均工资,再和员工表做匹配,避免对每条员工记录都执行一次子查询,提升查询性能。
如果子查询的结果集很小,优化器也可能选择先执行子查询,将结果缓存,再执行外层查询,和不相关子查询的执行逻辑类似。我们可以通过EXPLAIN命令查看数据库对子查询的执行计划,了解子查询的实际执行方式,针对性做优化。
-- 查看子查询的执行计划
EXPLAIN
SELECT e.emp_id, e.emp_name, e.salary, e.dept_id
FROM employee e
WHERE e.salary > (
SELECT AVG(salary)
FROM employee
WHERE dept_id = e.dept_id
);
子查询编写注意事项
编写子查询时需要注意几个问题,避免写出性能问题或者逻辑错误的语句。首先,相关子查询如果外层表数据量很大,子查询又比较复杂,会导致查询性能非常差,这种场景下尽量考虑重写为连接查询。其次,子查询作为数据源放在FROM子句后面时,必须给子查询起别名,否则数据库会报错。另外,子查询返回的结果类型需要和外层查询的使用场景匹配,比如标量子查询用在比较场景中时,必须保证子查询最多返回一条记录,否则会报单行子查询返回多行的错误。