last_value默认窗口帧为rows between unbounded preceding and current row,故每行结果即本行值;取分组真实末值须显式指定unbounded following,但兼容性差;first_value配合order by desc更安全直观;取完整最新记录应使用row_number()筛选。

LAST_VALUE 默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
这不是 bug,是 SQL 标准定义的行为:每计算一行,窗口就动态截断到该行为止。哪怕你写了 ORDER BY updated_at DESC,LAST_VALUE(status) 也只在“已扫描过的行”里找“最后一个”,当前行自然就是那个小窗口里的末尾值。
常见错误现象包括:
- 每行输出的
LAST_VALUE(status)都和本行status一模一样 - 加了
PARTITION BY user_id却发现各组末值不一致,甚至为NULL - 时间字段重复时结果随机——因为默认帧下“同排序值的行”可能被当作同一位置处理
想取分组真实末值必须显式写 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
只写 PARTITION BY 和 ORDER BY 不够,漏掉帧定义就等于没改默认行为。正确写法是:
LAST_VALUE(amount) OVER ( PARTITION BY user_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING )
但要注意:
- 某些旧环境(如 MySQL 8.0 之前、SQLite)根本不支持
UNBOUNDED FOLLOWING,会直接报错或静默忽略 -
ORDER BY方向必须匹配业务语义:要最新记录,得用created_at DESC;若用升序,LAST_VALUE反而取最早那条 - 时间字段有重复时,必须追加唯一列兜底,例如
ORDER BY created_at DESC, id DESC
FIRST_VALUE + ORDER BY DESC 更安全、更少出错
多数人真正想要的是“最新一条”,本质是“按时间倒序排的第一条”。FIRST_VALUE 天然契合这个语义,且默认帧就覆盖整组,无需手动补 ROWS 子句:
FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY updated_at DESC)
优势明显:
- 兼容性更好:MySQL 8.0+、PostgreSQL、SQL Server、BigQuery 都稳定支持
- 不易混淆:降序 + “第一个” = 最新,逻辑直觉清晰
- 避免
RANGE带来的重复值陷阱(RANGE会把相同时间戳的行视为同一位置)
LAST_VALUE 无法直接取整行,别硬套
LAST_VALUE 是逐列计算的函数。你写十个 LAST_VALUE(col1)、LAST_VALUE(col2),它们可能来自不同物理行——尤其当 ORDER BY 字段不唯一时,字段错位风险极高。
真正要取“最新订单的完整记录”,应该用:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, id DESC) AS rn
再外层 WHERE rn = 1。复杂点在于:窗口函数的行为不只取决于 ORDER BY,还强依赖帧定义;而“最后一条记录”这个目标,常需要排序确定性、去重兜底、以及行级筛选——不是单个函数能解决的。










