窗口函数需严格遵循语法顺序:partition by 必须在 order by 前;lag/lead 的偏移量须为正整数常量并配默认值;string_agg 排序须写在函数内;取 top n 优先用 row_number() 并确保排序确定性。

窗口函数里 OVER 子句写错顺序,结果就全乱了
窗口函数不是加个 OVER 就能用对的。常见错误是把排序和分组逻辑混在一起,比如写成 OVER (ORDER BY date) 却没加 PARTITION BY,结果整张表被当成一个大组排序,根本不是你想要的“每组内排序”。
真正要分组内转换,必须明确两件事:按什么字段分组(PARTITION BY),再在组内按什么排序或计算(ORDER BY 或直接聚合)。顺序不能颠倒,语法上 PARTITION BY 必须在 ORDER BY 前面。
-
PARTITION BY user_id是分组前提,漏掉它,所有行就挤在一个逻辑组里 - 如果只写
ORDER BY created_at不带PARTITION BY,ROW_NUMBER()会从全表第一行开始编号,不是每个用户从 1 开始 - PostgreSQL 和 MySQL 8.0+ 支持完整语法;SQLite 仅支持基础窗口函数,不支持
FRAME子句
LAG / LEAD 取不到前一行?默认值和偏移量容易设反
用 LAG(value, 1) 想取上一行的值,但首行总是 NULL——这不是 bug,是设计如此。窗口函数不会“跨组”拿数据,更不会自动补默认值。
关键在第二个参数(偏移量)和第三个参数(默认值)。很多人写成 LAG(value, 0) 以为能取当前行,其实那是无效偏移;也有人漏掉第三个参数,导致分组首行无法回退,直接丢空。
-
LAG(value, 1, 0)表示:取前 1 行的value,取不到就填0 -
LEAD(amount, 2, 0.0)表示:取后 2 行的amount,越界就填0.0 - 偏移量必须是常量正整数,不能是列名或表达式,否则报错
ERROR: argument of LAG must be a constant
用 STRING_AGG 拼接分组字符串,排序不生效?得靠 ORDER BY 写进函数里
STRING_AGG 的排序逻辑不在 OVER 子句里,而是在函数参数内部。写成 STRING_AGG(tag, ',') OVER (PARTITION BY id ORDER BY weight) 是错的——ORDER BY 在 OVER 里只影响窗口帧,不影响拼接顺序。
正确写法是把排序塞进函数第一个参数后面,用 ORDER BY 子句紧跟着,且必须用括号包裹整个聚合表达式。
- ✅ 正确:
STRING_AGG(tag, ',' ORDER BY weight) OVER (PARTITION BY id) - ❌ 错误:
STRING_AGG(tag, ',') OVER (PARTITION BY id ORDER BY weight) - MySQL 用
GROUP_CONCAT(tag ORDER BY weight SEPARATOR ','),语法位置类似,但不支持窗口模式 - PostgreSQL 中若未指定
ORDER BY,拼接顺序不确定,不同执行计划可能出不同结果
想用窗口函数做“分组内 Top N”,ROW_NUMBER() 比 RANK() 更可控
要取每个用户最近 3 条订单,用 RANK() 会因并列排名跳号,比如两个订单同时间,RANK() 给它们都标 1,下一个就是 3,结果只拿到 2 条;而 ROW_NUMBER() 强制唯一编号,稳稳取前 N。
但注意:编号依赖 ORDER BY 的确定性。如果排序字段有重复值又没加次级排序,不同执行可能生成不同编号。
- ✅ 推荐:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC) - ⚠️ 风险:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)—— 若时间相同,编号不稳定 - 过滤时必须用子查询或 CTE,因为窗口函数不能直接出现在
WHERE中:SELECT * FROM (SELECT *, ROW_NUMBER() ...) t WHERE rn
最常被忽略的是:窗口函数的执行时机晚于 WHERE 和 GROUP BY,早于 ORDER BY 和 LIMIT。这意味着你不能靠 WHERE 过滤窗口计算结果,也别指望在 GROUP BY 后再开窗——得先保证基表数据已按需筛选和关联好。










