FIRST_VALUE/LAST_VALUE默认窗口范围是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致LAST_VALUE仅返回当前行值而非分区末行值,须显式指定ROWS/RANGE UNBOUNDED FOLLOWING才能获取真正末尾值。

FIRST_VALUE / LAST_VALUE 的默认窗口范围是什么
这两个函数不加 ROWS 或 RANGE 显式声明时,MySQL 和 PostgreSQL 默认用 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这意味着 FIRST_VALUE 总是取当前行及之前所有行里的第一个值(通常没问题),但 LAST_VALUE 取的只是“到当前行为止”的最后一个值——不是整组最后一行,而是当前行自己。
常见错误现象:LAST_VALUE(col) 返回每行都是它自己的值,看起来像没生效。
- 必须显式写成
LAST_VALUE(col) OVER (ORDER BY x ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 或简写为
LAST_VALUE(col) OVER (ORDER BY x RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)(注意:RANGE 在有重复排序值时可能合并行,优先选 ROWS) - PostgreSQL 13+ 支持
RESPECT NULLS/IGNORE NULLS,但 MySQL 目前不支持
ORDER BY 缺失或不唯一导致结果不稳定
窗口函数依赖确定性排序。如果 ORDER BY 列存在重复值(比如多个事件同秒发生、多个用户同名),数据库可能任意决定行序,FIRST_VALUE 和 LAST_VALUE 就会随机挑一个“第一”或“最后”。这不是 bug,是未定义行为。
- 务必在
ORDER BY中加入能唯一标识行的列,例如ORDER BY event_time, event_id - 避免只用
ORDER BY user_id—— 这会让整个分区变成无序块,窗口范围退化为全分区无序,结果不可预测 - 测试时可用
ROW_NUMBER() OVER (...)辅助验证排序是否真有序
和 LAG/LEAD 混用时的逻辑错位
有人想用 FIRST_VALUE 替代 LAG 实现“上一行值”,这是典型误用。FIRST_VALUE 是取窗口内第一个,不是上一行;LAG(col, 1) 才是真正向前偏移 1 行。
-
FIRST_VALUE(x) OVER (ORDER BY ts)→ 永远返回该窗口最早时间那行的x -
LAG(x) OVER (ORDER BY ts)→ 返回紧挨着当前行的前一行的x - 若窗口是
PARTITION BY user_id ORDER BY ts,且某用户只有 1 条记录,FIRST_VALUE和LAST_VALUE都返回它自己,但LAG返回NULL
MySQL 5.7 不支持窗口函数,别被语法骗了
MySQL 5.7 用户看到 FIRST_VALUE 报错 FUNCTION xxx.FIRST_VALUE does not exist 或直接语法错误,不是写错了,是压根不支持。这个函数从 MySQL 8.0.2 才正式引入。
- 确认版本:
SELECT VERSION(); - 5.7 及更早:只能用变量模拟,例如
@first := IF(@grp = user_id, @first, col)配合子查询排序 - 替代方案中,
GROUP_CONCAT(col ORDER BY ts LIMIT 1)可勉强模拟FIRST_VALUE,但性能差、长度受限、无法处理 NULL
最容易被忽略的是:即使写了 ORDER BY,如果没配 ROWS 范围,LAST_VALUE 就永远取不到真正的“末尾”。这不是数据问题,是窗口定义没到位。

















