LAST_VALUE默认返回当前行值,因其默认窗口帧为RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW;要取分组末值须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING并确保ORDER BY具有确定性。

LAST_VALUE为什么总等于当前行值
因为默认窗口帧是 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW(MySQL/SQL Server/Hive 均如此),不是整组,而是“从开头到当前行”这个动态范围。哪怕你写了 ORDER BY create_time DESC,LAST_VALUE(col) 仍只在已扫过的行里找“最后一个”,自然就是当前行自己。
常见错误现象:
- 每行输出的
LAST_VALUE(status)都和本行status一样 - 分组内多行时间相同,结果随机或重复
- 加了
PARTITION BY却发现各组末值不一致,甚至为NULL
必须显式写ROWS BETWEEN才能取整组末值
RANGE 按排序列的值去归并(比如所有 create_time = '2024-01-01' 算同一组),而 ROWS 按物理行位置算——要稳定取“最后一行”,必须用 ROWS + 明确边界。
实操建议:
- 把
LAST_VALUE(col) OVER (PARTITION BY g ORDER BY t)改成:LAST_VALUE(col) OVER (PARTITION BY g ORDER BY t ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 如果排序字段可能重复(如多个记录同秒创建),务必加二级排序,例如:
ORDER BY create_time DESC, id DESC - 别依赖
RANGE:它在时间戳重复时会把多行视为“同一位置”,导致LAST_VALUE返回任意一行,不可控
FIRST_VALUE + 降序比LAST_VALUE更安全
多数人想取“最新一条”,本质是“按时间倒序后的第一条”。用 FIRST_VALUE 天然匹配这个语义,且默认帧 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 就够用,不用额外写 UNBOUNDED FOLLOWING。
对比示例:
- 危险写法(漏帧定义):
LAST_VALUE(name) OVER (PARTITION BY dept ORDER BY hire_date) - 安全写法:
FIRST_VALUE(name) OVER (PARTITION BY dept ORDER BY hire_date DESC) - 若还需整行数据(不止一个字段),
FIRST_VALUE仍需配合ROW_NUMBER()过滤,不能只靠它返回多列
真正要取“满足条件的最后一条整行”,别硬套LAST_VALUE
LAST_VALUE 是逐列计算的:你写十个 LAST_VALUE(x), LAST_VALUE(y),它们可能来自不同行——尤其当排序键不唯一时。它解决不了“取某状态的最新完整记录”这种需求。
正确路径:
- 先过滤再编号:
ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY update_time DESC, id DESC) - 外层加条件:
WHERE status = 'active' AND rn = 1 - 注意:窗口计算发生在
WHERE之前,不能把status = 'active'写在OVER里 - MySQL 8.0+、PostgreSQL、SQL Server 全支持,无兼容性风险
最易被忽略的一点:就算你补上了 ROWS BETWEEN ... UNBOUNDED FOLLOWING,只要 ORDER BY 缺少确定性(比如没加二级排序),数据库仍可能任意选一行作为“最后”,结果不可复现。

















