LAST_VALUE默认窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,故结果恒等于当前行值;需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能获取分组内真正最后一行。

因为默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,它只包含当前行及之前所有行,“最后”自然就是当前行自己。
LAST_VALUE 默认窗口帧到底是什么
几乎所有主流 SQL 引擎(PostgreSQL、SQL Server、MySQL 8.0+、Oracle)都遵循 SQL 标准:对 LAST_VALUE() 不显式指定窗口帧时,隐含使用 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(部分引擎用等效的 RANGE 版本)。这不是 bug,是设计行为。
- 这个范围意味着:每处理一行,窗口就从分区开头“滑动”到当前行
-
LAST_VALUE()只在这个滑动段里找“最后一个”,所以结果恒等于当前行值 - 加了
PARTITION BY和ORDER BY也无效——只要没改帧,仍是逐行截断计算
为什么显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 才管用
要取分组内真正最后一行的值,窗口必须覆盖整组所有行,不能只看“已扫过的部分”。
- 必须补全
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,否则LAST_VALUE()永远看不到后面的行 -
ORDER BY方向决定语义:ORDER BY created_at(升序)时,LAST_VALUE()对应最大时间;ORDER BY created_at DESC时反而对应最小时间——容易反直觉 - 如果
created_at有重复值,数据库可能任意决定行序,导致“最后一行”不稳定;务必加二级排序,例如ORDER BY created_at, id
为什么 FIRST_VALUE + ORDER BY DESC 更常用也更安全
多数人真正要的是“最新一条”,本质是“按时间倒排后的第一条”。用 FIRST_VALUE() 配合降序,语义更直接,出错概率更低。
-
FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at DESC)默认帧就覆盖整组,不用手动补ROWS子句 - 漏写
ROWS也不影响结果,兼容性更好(比如 MySQL 5.7、SQLite 不支持窗口帧子句,但支持FIRST_VALUE) - 注意空值:
created_at可为 NULL 时,建议显式写ORDER BY created_at DESC NULLS LAST(PostgreSQL)或用COALESCE(created_at, '1970-01-01')(MySQL)
想取整行最新记录,别硬套 LAST_VALUE
LAST_VALUE() 是逐列计算的。你写十个 LAST_VALUE(x)、LAST_VALUE(y),它们可能来自不同行——尤其当排序键不唯一时,字段错位风险极高。
- 真正要拿最新那条完整记录(比如
id、amount、status),应该用ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC, id DESC),再外层WHERE rn = 1 - 强行用多个
LAST_VALUE()拼整行,既不可靠,又难维护,还可能因排序不确定性导致结果每次执行都不一样
最常被忽略的一点:窗口函数依赖确定性排序。如果 ORDER BY 列存在大量重复(比如只按 status 排),不同数据库对“相等行顺序”的处理不一致,LAST_VALUE 可能指向任意一个同 status 的行——补一个唯一列(如 id 或 ctid)兜底才是关键。

















