LAST_VALUE默认返回当前行值,因其窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,仅覆盖当前行及之前行;要取分组末值,须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,并用ORDER BY定义明确顺序。

LAST_VALUE 为什么总等于当前行的值
因为默认窗口框架是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,它只包含“从分区开头到当前行(含)”的数据——当前行自然就是这个范围里的“最后”,所以每行的 LAST_VALUE() 都返回自己。这不是 bug,是标准定义,但极易误用。
- ORDER BY 字段有重复值时,同组“peer rows”会被视为同一位置,
CURRENT ROW可能实际覆盖多行,加剧结果不可控 - 即使加了
PARTITION BY,只要没改窗口帧,仍是逐行截断计算 - PostgreSQL、MySQL 8.0+、SQL Server 行为一致,别指望某家“更智能”
必须显式写全 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
要让 LAST_VALUE() 真正取到整个分组最后一行的值,窗口范围必须明确覆盖全部行。只写 ORDER BY 不够,PARTITION BY 也不够,缺了这一句就白搭。
- 用
ROWS而非RANGE:避免因排序字段重复导致意外聚合(RANGE会把相同排序值的行全纳入) - 示例:
LAST_VALUE(score) OVER (PARTITION BY class_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 注意
created_at必须有索引,否则大表上OVER计算开销陡增
FIRST_VALUE 降序替代 LAST_VALUE 更安全
如果目标只是取分组内按时间/序号排的“最新一条”,用 FIRST_VALUE(col) OVER (PARTITION BY x ORDER BY ts DESC) 比硬套 LAST_VALUE 更省心——它默认窗口帧虽也是 CURRENT ROW,但按降序排后,“第一个”天然对应最新,无需手动扩帧。
- 降序 +
FIRST_VALUE的组合,在 MySQL 8.0、PostgreSQL、BigQuery 上默认行为稳定 - 当排序字段可能为 NULL 时,
ORDER BY ts DESC NULLS LAST显式控制空值位置,否则 PostgreSQL 可能把 NULL 当最大值 - 若需兼容 MySQL 5.7 或 SQLite JOIN + 子查询找 max(ts)
ORDER BY 字段不稳定时 LAST_VALUE 结果不可靠
窗口函数依赖确定性排序。如果 ORDER BY 列存在大量重复(比如只按 status 排序),不同数据库对“相等行顺序”的处理不一致,LAST_VALUE 可能指向任意一个同 status 的行,且每次执行结果未必相同。
- 修复方法:补一个唯一列兜底,例如
ORDER BY status, id或ORDER BY updated_at, rowid - 在 PostgreSQL 中,
ORDER BY updated_at, ctid是常见兜底方案;MySQL 可用ORDER BY updated_at, primary_key - 别依赖 SELECT 语句末尾的
ORDER BY来修正窗口内顺序——窗口计算发生在结果排序之前
LAST_VALUE 开始随机漂移——这种问题往往压根不会报错,只悄悄污染业务指标。

















