窗口函数是什么
窗口函数在保留原始行的前提下做排名、累计与相邻行取值:它按分区切分数据、按排序定义顺序,然后把计算结果附加到每一行,而不像分组聚合那样把多行折叠成一行。MySQL 8.0 起支持,是升级到 8.0 的重要理由之一。
基本信息
| 版本要求 | MySQL 8.0 及以上,5.7 及以下不识别语法 |
|---|---|
| 排名类 | ROW_NUMBER()、RANK()、DENSE_RANK()、NTILE() |
| 取值类 | LAG()、LEAD()、FIRST_VALUE()、LAST_VALUE() |
| 聚合类 | SUM/AVG/COUNT/MAX/MIN 后加 OVER 子句 |
| 子句结构 | OVER (PARTITION BY 分组列 ORDER BY 排序列 帧定义) |
典型使用场景
- 分组取最新一条:按用户分区、按时间倒序编号,取编号为 1 的记录
- 排行榜并列处理:需要名次不跳号用密集排名,需要跳号用普通排名
- 同比环比:用 LAG 或 LEAD 取上一行或下一行的值直接算差值
- 累计与移动平均:按时间排序做累计求和或滚动窗口平均
- 去重取一条:同一业务键有多条记录时,按规则编号后只保留第一条
常用写法
常用写法
-- 每个用户取最近一单
SELECT * FROM (
SELECT o.*, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders o
) t WHERE rn = 1;
-- 累计金额与环比
SELECT id, amount,
SUM(amount) OVER (ORDER BY id) AS cum_amount,
LAG(amount) OVER (ORDER BY id) AS prev_amount
FROM orders;排名函数对照
| 函数 | 并列值处理 | 名次序列示例 |
|---|---|---|
| ROW_NUMBER | 并列也给不同编号 | 1 2 3 4 |
| RANK | 并列同名次,后续跳号 | 1 1 3 4 |
| DENSE_RANK | 并列同名次,后续不跳号 | 1 1 2 3 |
| NTILE(n) | 按顺序切分成 n 段 | 均匀分桶,用于分位数 |
窗口函数不能写在 WHERE 子句里:执行顺序在条件过滤之后,必须用外层子查询或公共表表达式先算出编号再过滤。
没有 ORDER BY 时窗口覆盖整个分区,结果与分区聚合相同;加上 ORDER BY 后默认帧是从分区开头到当前行,累计值会随行推进。
在低版本实例上执行窗口语法会直接报语法错误,升级前先用 SELECT VERSION() 确认版本。
改写对照
- 分组取最新一条:旧写法是自连接求最大值,新写法是分区编号后取编号为 1
- 累计求和:旧写法依赖变量逐行累加,顺序不保证时结果会错,窗口函数无此问题
- 同环比:旧写法靠自连接错位关联,窗口函数用取上一行函数一次搞定
- 去重保留一条:旧写法用临时表加分组,新写法在外层过滤编号