SQL子查询和窗口函数都是处理复杂查询逻辑的常用工具,两者在部分场景下可以实现相同的功能,但核心设计目标和适用场景存在明显差异,并非所有情况都能相互替代。

子查询与窗口函数的核心特性
子查询的核心特点
子查询是嵌套在主查询内部的查询语句,会先执行子查询得到结果集,再将该结果集作为主查询的输入条件。子查询可以分为标量子查询、行子查询、表子查询等类型,常用于过滤、计算字段、关联查询等场景。子查询的执行逻辑相对独立,部分场景下会导致多次扫描数据表,性能开销相对较高。
窗口函数的核心特点
窗口函数是SQL中用于在不减少原表行数的前提下,对数据进行分组、排序、计算的函数,常见的窗口函数包括ROW_NUMBER()、RANK()、SUM() OVER()等。窗口函数会基于指定的窗口范围对数据进行计算,一次扫描即可完成所有计算,不会过滤原表的行数,适合需要保留明细数据同时获取统计结果的场景。
可以相互替代的场景
当需要实现分组内排名、分组内累计计算等需求,且不需要保留原表所有行时,子查询和窗口函数可以实现相同的效果。
示例:查询每个部门薪资最高的员工信息
使用子查询实现:
-- 子查询实现:先查询每个部门的最高薪资,再关联原表获取对应员工信息
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
) t ON e.dept_id = t.dept_id AND e.salary = t.max_salary;
使用窗口函数实现:
-- 窗口函数实现:用DENSE_RANK按部门分组降序排名,取排名为1的员工
SELECT
emp_id,
emp_name,
dept_id,
salary
FROM (
SELECT
emp_id,
emp_name,
dept_id,
salary,
DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS salary_rank
FROM
employee
) t
WHERE
salary_rank = 1;
上述两种方案都能得到每个部门薪资最高的员工信息,在这个场景下两者可以相互替代,不过窗口函数的写法更简洁,且只需要一次全表扫描,性能通常更优。
无法相互替代的场景
子查询独有的适用场景
当需要实现EXISTS、NOT EXISTS逻辑判断,或者需要作为过滤条件判断某个值是否存在于子查询结果中时,子查询是更合适的选择,窗口函数无法直接实现这类逻辑。
示例:查询有员工薪资大于10000的部门信息
-- 使用子查询实现EXISTS逻辑
SELECT
dept_id,
dept_name
FROM
department d
WHERE
EXISTS (
SELECT 1
FROM employee e
WHERE e.dept_id = d.dept_id AND e.salary > 10000
);
这个场景下窗口函数无法替代子查询,因为窗口函数无法生成用于EXISTS判断的布尔结果。
窗口函数独有的适用场景
当需要保留原表所有明细行,同时获取分组内的统计结果、前后行数据对比时,窗口函数是唯一选择,子查询无法实现这类需求,因为子查询会合并分组数据,导致明细行丢失。
示例:查询每个员工的薪资,以及所在部门的平均薪资,同时保留所有员工明细
-- 使用窗口函数实现,保留所有员工明细
SELECT
emp_id,
emp_name,
dept_id,
salary,
AVG(salary) OVER (PARTITION BY dept_id) AS dept_avg_salary
FROM
employee;
如果使用子查询实现,需要先按部门分组计算平均薪资,再关联原表,虽然也能得到结果,但需要额外的关联操作,逻辑更复杂,而窗口函数可以直接在一次扫描中完成计算。
选择建议
在实际开发中,可以遵循以下原则选择:
- 如果需要保留原表所有明细行,同时获取分组统计、排名、前后行数据,优先选择窗口函数
- 如果需要实现
EXISTS、NOT EXISTS逻辑判断,或者需要子查询作为独立的过滤条件,优先选择子查询 - 在两者都能实现的场景下,优先选择窗口函数,因为性能通常更优,代码也更简洁