last_value默认窗口帧是rows between unbounded preceding and current row,仅覆盖当前行及之前行,故返回当前行值;要获取分组末尾值,须显式指定rows between unbounded preceding and unbounded following,并配合确定性order by。

LAST_VALUE 默认窗口帧是什么?
默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是整组,只包含当前行及之前所有行。所以哪怕你写了 ORDER BY created_at,LAST_VALUE(amount) 在每一行都只返回“到这行为止”的最后一个 amount——大概率就是当前行自己的值。
怎么写才真正拿到分组末尾值?
必须显式指定完整窗口范围:
- 用
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,确保覆盖整个PARTITION BY分组 -
ORDER BY不可省:比如按时间取“最后”,就得明确写ORDER BY created_at(升序)或ORDER BY created_at DESC(降序),语义要和业务一致 - 如果排序列有重复(如多个记录同秒创建),结果可能不稳定;加二级排序,例如
ORDER BY created_at, id - NULL 值会影响排序位置:PostgreSQL 默认
NULLS LAST,MySQL 8.0 默认NULLS FIRST,必要时显式写NULLS LAST避免错位
为什么有人建议改用 FIRST_VALUE + 逆序?
因为 FIRST_VALUE 在默认帧下就能工作,不用记 ROWS BETWEEN 这一长串:
-
FIRST_VALUE(amount) OVER (PARTITION BY category ORDER BY created_at DESC)等价于取每组最新一条的amount - 语义更直白:“我要第一,那就把时间倒过来排”
- 兼容性更好:SQLite 不支持
LAST_VALUE的窗口帧子句,MySQL 8.0+ 才完整支持,而FIRST_VALUE在主流引擎中行为更统一 - 不依赖
IGNORE NULLS(PostgreSQL 支持,MySQL 不支持),避免跨库迁移踩坑
想取整行数据,别硬套 LAST_VALUE
LAST_VALUE 只能返回单个字段值。若要拿到最新记录的 id、status、updated_at 全部字段,强行对每个字段单独调用 LAST_VALUE 很危险:
- 排序键重复时,不同字段可能来自不同原始行,造成数据错配
- 性能差:多个窗口函数重复扫描排序结果
- 正确做法是用
ROW_NUMBER()标序再过滤:ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, id DESC),外层WHERE rn = 1 - 这个方案在 PostgreSQL、SQL Server、MySQL 8.0+、DuckDB 上都可靠,且逻辑清晰
窗口帧不是语法装饰,它直接决定 LAST_VALUE 看得到哪些行——漏写 ROWS BETWEEN 是最隐蔽也最常复现的错误。










