窗口函数
窗口函数读取与当前行相关的一组行,计算一个值,再把它写回当前行。它可以计算累计和、排名,同时保留每个源行;GROUP BY 则将每组缩成聚合结果,改变结果粒度。
OVER 分成三个问题:PARTITION BY 决定哪些行一起计算;内部 ORDER BY 决定计算顺序;函数与适用的 frame 决定当前值读取哪些有序行。窗口内部排序不保证最终返回顺序,需要时还要查询级 ORDER BY。
累计 SUM 如何工作
本课显式指定范围:
SELECT
id,
region,
day,
amount,
SUM(amount) OVER (
PARTITION BY region
ORDER BY day, id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_sum
FROM sales
ORDER BY region, day, id;
内部 ORDER BY day, id 决定累计前缀,外部 ORDER BY region, day, id 决定返回顺序。每个当前行的 ROWS 范围从本地区首行开始,到当前行结束,SUM 只累加这些成员的 amount。
窗口函数逐步演示
默认源表故意交错:
| id | region | day | amount |
|---|---|---|---|
| s1 | North | 3 | 30 |
| s2 | South | 1 | 40 |
| s3 | North | 1 | 30 |
| s4 | South | 3 | 40 |
| s5 | North | 2 | 50 |
| s6 | South | 2 | 10 |
| s7 | North | 4 | 20 |
按 region 分区,再按 day、id 排序:North 为 s3 → s5 → s1 → s7,South 为 s2 → s6 → s4。
- North 的 s3 读取
[s3],得到 30。 - s5 读取
[s3, s5],30 + 50 = 80。 - s1 读取
[s3, s5, s1],30 + 50 + 30 = 110。 - s7 读取
[s3, s5, s1, s7],30 + 50 + 30 + 20 = 130。 - South 从零重新开始,窗口顺序的累计值为 40、50、90。
按行 ID 写回,教学表仍按源顺序排列:
| id | region | day | amount | running_sum |
|---|---|---|---|---|
| s1 | North | 3 | 30 | 110 |
| s2 | South | 1 | 40 | 40 |
| s3 | North | 1 | 30 | 30 |
| s4 | South | 3 | 40 | 90 |
| s5 | North | 2 | 50 | 80 |
| s6 | South | 2 | 10 | 50 |
| s7 | North | 4 | 20 | 130 |
这里保持源顺序是为了核对身份,不表示 SQL 在没有外部 ORDER BY 时也保证此顺序。
RANK 与 DENSE_RANK
排名沿用分区和排序,但不使用聚合行范围:
SELECT
id,
region,
amount,
RANK() OVER (
PARTITION BY region
ORDER BY amount DESC
) AS rank
FROM sales;
内部 ORDER BY 值相同的行是同级行,得到相同排名。当前按 amount 降序:RANK 为 1 加前面金额严格更大的行数;DENSE_RANK 为 1 加前面不同金额的种数。
North 顺序为 s5(50), s1(30), s3(30), s7(20)。两个 30 都为 2,下一行 RANK=4,因为前面三行;DENSE_RANK=3,因为前面只有两个不同金额。South 为 s2(40), s4(40), s6(10),两个 40 并列,10 的 RANK=3、DENSE_RANK=2。
| id | RANK | DENSE_RANK |
|---|---|---|
| s1 | 2 | 2 |
| s2 | 1 | 1 |
| s3 | 2 | 2 |
| s4 | 1 | 1 |
| s5 | 1 | 1 |
| s6 | 3 | 2 |
| s7 | 4 | 3 |
宽同级组预设让 West 四行以 90 并列,后两行 RANK 为 5、6,DENSE_RANK 为 2、3,便于看出排名空缺。
窗口函数与 GROUP BY
窗口函数将 n 个源行变为 n 个附加计算列的行;GROUP BY region 将 n 行变成 p 个地区聚合行。默认窗口结果有七行,分组结果只有两行,后者不再保留 s1、s3 等逐行身份。
删掉 PARTITION BY 是另一种改变。分区重置预设中,全局 ORDER BY day, id 为 p2, p3, p1, p4,累计值 10, 110, 210, 215。首个 South 仍为 10,要等两地区共享前缀时才看到错误。正确分区后 North 为 100、200,South 为 10、15。
边界情况
- 空输入没有分区、范围、同级组或输出,窗口函数不凭空造行。
- 单行分区的累计范围只有自己,两种排名都为 1。
- 同级行共享排名,下一不同值才揭示是否跳号。
- ROWS 按位置定义,若 day 相同而先后会影响中间累计值,可加 id 作为确定性次序。
- 缺少外部 ORDER BY 时,内部顺序只约束计算,不约束返回展示。
常见错误
- 认为 PARTITION BY 合并源行,它只划定独立计算范围。
- 按源表顺序累计,实际应遵循 OVER 内的 ORDER BY。
- 省略 frame 后假设等于 ROWS。某些默认 RANGE 会把同值行一起纳入,与逐行前缀不同。
- 用不同值个数算 RANK,那是 DENSE_RANK;RANK 计前面行数,会跳号。
- 内部有 ORDER BY 就认为返回顺序稳定,需要外层排序。
- 把讲解工作台当物理排序或缓冲计划。SQL 定义逻辑,优化器选择执行。
模型范围
本课展示逻辑分区、窗口排序、显式前缀 ROWS、同级组、排名空缺、行身份和结果行数。省略执行策略、内存与落盘、排序规则、NULL 顺序、溢出精度、命名窗口、FILTER、其他边界、RANGE、GROUPS、排除子句和方言默认值。隐式 frame 与 NULL 排序应按目标数据库确认。