LAST_VALUE默认只取当前行及之前范围的末值,故每行返回自身值;要取分组真末值需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING;但更推荐用FIRST_VALUE配合ORDER BY ... DESC获取最新记录。

LAST_VALUE 默认只看“当前行及之前”,不是 bug,是设计如此——它根本没机会看到分组里后面的行。
LAST_VALUE 为什么每行都返回自己
直接写 LAST_VALUE(col) OVER (PARTITION BY group_col ORDER BY ts),结果每行的值都等于本行 col。这是因为默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW:从分组开头到当前行(含),当前行自然就是这个范围里的“最后一个”。哪怕你按时间升序排,它也不会跳到末尾去取;哪怕加了 PARTITION BY,只要没改帧,仍是逐行截断计算。
常见错误现象包括:
- 所有行输出的
LAST_VALUE(status)都和本行status一样 - 分组内多行
ts相同,结果随机或重复 - 加了
ORDER BY ts DESC却仍拿不到最新那条,而是返回当前行
要取真末值,必须显式写全窗口帧
想让它真正扫完整个分组,得手动补上 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。只改 ORDER BY、只加 PARTITION BY,都不起作用。
正确写法示例:
LAST_VALUE(amount) OVER ( PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )
注意两点:
-
ORDER BY方向决定“最后”的语义:升序时最大时间才是最后;降序时最小时间反而是最后——别写反 - 如果
created_at有重复(比如并发插入),不加二级排序(如id DESC)会导致结果不确定
用 FIRST_VALUE + 降序更安全
多数人真正想要的是“最新一条”,即“按时间倒序排的第一条”。FIRST_VALUE 天然匹配这个语义,而且它的默认帧虽也是 CURRENT ROW,但按降序排后,“第一个”就对应最新,无需手动扩帧。
推荐写法:
FIRST_VALUE(order_id) OVER ( PARTITION BY user_id ORDER BY created_at DESC, id DESC )
优势在于:
- 语义清晰:“取每组里时间最晚的那条的
order_id” - 主流引擎(MySQL 8.0+、PostgreSQL、BigQuery)行为统一
- SQLite 不支持
UNBOUNDED FOLLOWING,但支持此写法
别用 LAST_VALUE 取整行数据
LAST_VALUE 是单列函数,一次只能返回一个表达式的值。你写十个 LAST_VALUE(x)、LAST_VALUE(y),它们可能来自不同物理行——尤其当 ORDER BY 字段不唯一时,字段会错位。
真正要取最新订单的完整记录(比如 id, name, amount, created_at),优先用:
ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY created_at DESC, id DESC ) AS rn
再外层 WHERE rn = 1。这是唯一能保证字段来自同一原始记录的方式。
最容易被忽略的一点:即使你把 LAST_VALUE 的帧写对了,只要排序字段缺乏确定性(比如没加唯一二级键),结果就不可复现——不是数据库问题,是 SQL 标准本身允许这种不确定性。

















