last_value默认返回当前行值,因其隐式窗口帧为rows between unbounded preceding and current row;需显式指定rows between unbounded preceding and unbounded following才能获取分组末值,且推荐用first_value配合order by desc或row_number()替代以提升可靠性。

LAST_VALUE 默认只看“当前行及之前”,不是整组
LAST_VALUE 返回当前行数据,不是 bug,是 SQL 标准定义的默认行为:它的隐式窗口帧为 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这意味着每计算一行,窗口就“截止到当前行”,当前行自然就是这个小窗口里的“最后一个”。哪怕你写了 ORDER BY updated_at DESC,它也只在已扫过的行里找末尾,而不是整个分组。
- 错误写法:
LAST_VALUE(status) OVER (PARTITION BY user_id ORDER BY updated_at)→ 实际等价于加了默认帧,结果每行都等于本行status - 升序 vs 降序影响语义:按
updated_at ASC时,“最后”对应最大时间;按updated_at DESC时,“最后”反而是最早时间——别只改排序方向,帧必须同步调整 - 重复时间值会让问题更隐蔽:如果多条记录
updated_at相同,RANGE帧(某些引擎默认)会把它们全纳入同一位置,CURRENT ROW可能覆盖多行,结果随机
要取分组末值,必须显式指定窗口边界
想让 LAST_VALUE 真正看到整组所有行,得手动覆盖默认帧,用 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。这是硬性要求,缺了就无效。
- 正确写法:
LAST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY updated_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 优先用
ROWS而非RANGE:避免因排序列重复导致意外聚合(比如多个updated_at = '2024-01-01'被当同一位置处理) - PostgreSQL / MySQL 8.0+ / SQL Server 都支持该语法;SQLite 不支持窗口帧子句,此写法直接报错
LAST_VALUE 本质是逐列取值,不是取整行
它只返回单个表达式的值,不能“带出”某条完整记录。常见错误是并列写多个 LAST_VALUE(col1)、LAST_VALUE(col2),一旦排序不稳定或存在重复键,各字段可能来自不同物理行,造成数据错位。
- 想取最新订单的
id、amount、status三字段?LAST_VALUE不适合——它不保证三者来自同一行 - 安全替代方案:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, id DESC)+ 外层WHERE rn = 1 - 若仅需“最新时间对应的某个字段值”,且排序键唯一、无 NULL,
LAST_VALUE加显式帧可用;但一有重复或缺失,可靠性立刻下降
FIRST_VALUE 降序比 LAST_VALUE 更省心
多数人真正想要的是“最新一条”,语义上等价于“按时间倒序后的第一条”。用 FIRST_VALUE 配合 ORDER BY ts DESC,默认帧 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 就够用,不用额外写 UNBOUNDED FOLLOWING,出错概率更低。
- 安全写法:
FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY hire_date DESC, id DESC) - NULL 处理需显式:PostgreSQL 要加
NULLS LAST,MySQL 则默认把 NULL 当最小值,行为不一致 - 仍无法取整行:
FIRST_VALUE同样只返回单列值,整行需求还得靠ROW_NUMBER()
ORDER BY updated_at 不够,必须补一个唯一列(如 id)兜底,否则重复时间下结果不可复现。











