last_value默认返回当前行值,因其窗口帧为rows between unbounded preceding and current row;要取分组末值须显式指定unbounded following;推荐用first_value(... order by updated_at desc)替代,语义更清晰且无需额外帧定义。

LAST_VALUE 默认只看“当前行及之前”,不是 bug 是设计
LAST_VALUE 返回当前行值,是因为它默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW。这个范围从分组开头一直延伸到当前行(含),所以“最后”永远是当前行自己——哪怕你写了 ORDER BY updated_at DESC,它也只在已扫描过的行里找,根本看不到后面还没处理的记录。
- 写
LAST_VALUE(status) OVER (PARTITION BY user_id ORDER BY updated_at DESC)等价于显式加了默认帧,结果每行都等于本行status - 这个行为在 PostgreSQL、SQL Server、MySQL 8.0+、BigQuery 中完全一致,不是某家引擎的 bug
- 如果你看到所有行返回同一个值,大概率是窗口帧没改,而不是数据或排序出了问题
要取整组末值,必须显式写 UNBOUNDED FOLLOWING
想让 LAST_VALUE 真正覆盖整个分组,得手动补全窗口定义:把下界设为 UNBOUNDED PRECEDING,上界设为 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方向决定“最后”的语义:升序时最大时间才是最后;降序时最小时间反而是最后——别写反了 - SQLite 和 MySQL 8.0 之前不支持
UNBOUNDED FOLLOWING,该写法会报错或被静默忽略
FIRST_VALUE + DESC 比 LAST_VALUE 更少出错
多数人真正想要的是“最新一条”,也就是“按时间倒序排的第一条”。FIRST_VALUE 天然匹配这个语义,而且它的默认帧就是整组,不用额外写 ROWS 子句。
- 推荐写法:
FIRST_VALUE(status) OVER (PARTITION BY user_id ORDER BY updated_at DESC) - 排序字段重复时务必加二级排序,例如:
ORDER BY updated_at DESC, id DESC -
FIRST_VALUE在主流引擎中行为统一;LAST_VALUE在 SQLite 中甚至不支持帧子句,直接不可用
别用 LAST_VALUE 取整行,字段会错位
LAST_VALUE 是逐列计算的函数,不是行级操作。你写十个 LAST_VALUE(x)、LAST_VALUE(y),它们可能来自不同物理行——尤其当 ORDER BY 字段不唯一时。
- 写
LAST_VALUE(id), LAST_VALUE(amount), LAST_VALUE(status)无法保证这三列来自同一原始记录 - 真正要取最新订单的完整记录,优先用:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, id DESC) AS rn,再WHERE rn = 1 - 如果表很大且
updated_at没索引,OVER计算开销会陡增,而ROW_NUMBER()同样受此影响
ORDER BY 和窗口帧共同决定,而“最后一条记录”这个业务目标,常被误认为靠一个函数就能解决——实际上它需要排序确定性、去重兜底、以及行级筛选三者配合。











