SQL窗口函数是处理复杂聚合、排序场景的重要工具,它的执行顺序和常规SQL子句的执行逻辑紧密相关,理解这个顺序能帮助我们写出正确的查询语句,也能为SQL优化提供方向。

SQL语句的完整标准执行顺序
一条完整的SQL查询语句,从解析到返回结果,会按照固定的阶段依次处理,标准执行顺序如下:
- FROM:确定查询的数据源,包括表连接操作
- ON:处理表连接的条件
- JOIN:完成表连接,生成临时结果集
- WHERE:过滤临时结果集的行
- GROUP BY:对结果集按指定列分组
- 聚合函数计算:执行COUNT、SUM等聚合函数的计算
- HAVING:过滤分组后的结果
- SELECT:选择需要返回的列,此时窗口函数会在这个阶段执行
- DISTINCT:对结果去重
- ORDER BY:对最终结果排序
- LIMIT:限制返回的行数
窗口函数的具体执行时机
窗口函数的执行发生在SELECT子句处理阶段,也就是在WHERE、GROUP BY、HAVING这些过滤和分组操作完成之后才会执行。这意味着窗口函数计算时,能拿到已经经过前期过滤、分组后的最终结果集,不会受到后续ORDER BY、LIMIT子句的影响。
需要注意的是,窗口函数不会像GROUP BY那样把多行合并成一行,它会为每一行返回一个计算结果,结果行数和输入行数保持一致。
示例验证执行顺序
我们创建一个员工表作为示例,表结构如下:
-- 创建员工表
CREATE TABLE employee (
id INT PRIMARY KEY,
dept VARCHAR(20),
salary INT
);
-- 插入测试数据
INSERT INTO employee VALUES
(1, '研发部', 8000),
(2, '研发部', 10000),
(3, '市场部', 7000),
(4, '市场部', 9000),
(5, '研发部', 12000);
执行以下查询语句,观察窗口函数的执行效果:
SELECT
dept,
salary,
-- 窗口函数:计算部门内薪资排名
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_salary_rank
FROM employee
WHERE salary > 7500
ORDER BY dept, dept_salary_rank;
这个查询的执行流程是:
- FROM employee:加载员工表全部数据
- WHERE salary > 7500:过滤掉薪资小于等于7500的行,此时id为3的员工(薪资7000)会被过滤掉
- SELECT子句处理:先选择dept、salary列,再执行RANK()窗口函数,按dept分区、薪资降序排名,此时参与计算的是过滤后的4行数据
- ORDER BY dept, dept_salary_rank:对窗口函数计算后的结果排序,返回最终结果
最终查询结果如下:
| dept | salary | dept_salary_rank |
|---|---|---|
| 研发部 | 12000 | 1 |
| 研发部 | 10000 | 2 |
| 研发部 | 8000 | 3 |
| 市场部 | 9000 | 1 |
基于执行顺序的优化思路
理解窗口函数的执行顺序后,我们可以从以下几个方向优化SQL查询:
提前过滤数据减少窗口函数计算量
因为窗口函数在WHERE之后执行,所以尽量把能提前过滤的条件放到WHERE子句中,减少参与窗口函数计算的行数,提升执行效率。比如上面的例子中,先过滤掉薪资低的员工,再计算部门排名,比先算排名再过滤效率更高。
避免在窗口函数中做不必要的复杂计算
窗口函数的计算是针对每一行进行的,如果窗口函数里的逻辑过于复杂,会明显增加执行耗时。如果可以在前期预处理的计算,不要放到窗口函数里处理。
合理选择窗口范围
如果只需要计算部分行的聚合结果,比如最近3行的平均值,可以通过ROWS BETWEEN 2 PRECEDING AND CURRENT ROW这样的子句限制窗口范围,减少不必要的计算。
常见误区说明
很多开发者会误以为窗口函数可以在WHERE子句中使用,实际上因为窗口函数在SELECT阶段才执行,WHERE阶段还没有窗口函数的计算结果,所以不能在WHERE里引用窗口函数的别名或者结果。如果需要根据窗口函数的结果过滤,需要把查询作为子查询,在外层使用WHERE过滤:
SELECT *
FROM (
SELECT
dept,
salary,
RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_salary_rank
FROM employee
) t
WHERE dept_salary_rank = 1;
这个查询先在内层子查询中计算排名,外层再过滤排名第一的员工,是符合执行顺序的正确写法。