LAST_VALUE默认不返回最新值,因其窗口帧为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,仅覆盖当前行及之前行;需显式指定RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并配合确定性ORDER BY(如updated_at, id)才能取到分组真正末值。

LAST_VALUE 为什么默认不返回最新值
直接写 LAST_VALUE(status) 往往拿不到你想要的“最新状态”,因为它的默认窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW —— 也就是说,它只看从开头到当前行的子集,而“最后”指的是这个子集里的最后一行,不是整个分区的最后一条。
常见错误现象:按时间排序后,每行都返回自己这行的 status,或者返回中间某条,就是不返回真正的最新值。
- 必须显式指定窗口帧为
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - ORDER BY 必须明确、稳定(建议加上唯一键如
id防止并列排序歧义) - 如果按业务时间(如
updated_at)排序,注意时区和精度(微秒级并列仍可能出问题)
正确写法:带完整窗口定义的 LAST_VALUE
假设有一张设备状态表 device_status,含字段 device_id、status、updated_at,想为每个设备补上其最新状态:
SELECT
device_id,
status,
updated_at,
LAST_VALUE(status) OVER (
PARTITION BY device_id
ORDER BY updated_at, id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS latest_status
FROM device_status;
关键点:
-
PARTITION BY device_id确保分设备计算 -
ORDER BY updated_at, id解决时间相同的情况(避免 nondeterministic 结果) -
RANGE BETWEEN ... UNBOUNDED FOLLOWING才能让 LAST_VALUE 看到整组末尾
替代方案:用 FIRST_VALUE 配合倒序更可靠
很多工程师发现 LAST_VALUE 容易配错,干脆换思路:把顺序倒过来,用 FIRST_VALUE 更直观、容错性更高。
等价写法:
SELECT
device_id,
status,
updated_at,
FIRST_VALUE(status) OVER (
PARTITION BY device_id
ORDER BY updated_at DESC, id DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS latest_status
FROM device_status;
优势:
- 语义清晰:“取排序后第一行的状态” = 最新状态
- 不用记
LAST_VALUE的反直觉默认帧 - 数据库优化器对
FIRST_VALUE+ 降序的支持通常更成熟(尤其在 PostgreSQL 和 SQL Server 中)
性能与兼容性要注意什么
LAST_VALUE 和 FIRST_VALUE 在不同数据库里行为一致,但窗口帧语法支持度有差异:
- MySQL 8.0+ 支持完整
RANGE帧;MySQL 5.7 不支持窗口函数,直接报错ERROR 1064 - PostgreSQL 支持,但若省略帧,默认仍是
CURRENT ROW范围,结果不可靠 - SQLite 3.25+ 支持,但不支持
RANGE帧,只能用ROWS;此时必须写ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - Oracle 和 SQL Server 全支持,但 Oracle 对
NULLS LAST敏感,updated_at为 NULL 时可能排最前,影响“最新”判断
真实场景中,如果数据量大且频繁查询最新状态,别只靠窗口函数硬算——考虑加物化视图或单独维护一张 device_latest 表,否则每次查都在全量扫描。










