LAST_VALUE默认返回当前行值是因为窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW;取分组最后一行完整记录应使用ROW_NUMBER()配合WHERE rn=1,而非LAST_VALUE。

LAST_VALUE 不能直接“取分组最后一个值”,默认行为只返回当前行——因为窗口帧没设对,不是函数本身不行。
LAST_VALUE 为什么总返回当前行的值
直接写 LAST_VALUE(col) OVER (PARTITION BY group_col ORDER BY time_col),结果每行都等于 col 自身。这不是 bug,是默认窗口帧为 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 导致的:它只看“从分区开头到当前行”,当前行自然就是“最后”。
- 必须显式加
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才覆盖整组 - ORDER BY 方向决定“最后”语义:升序时最大时间才是最后;降序时最小时间反而是最后
- 如果
time_col有重复(比如并发插入),不加二级排序(如id DESC)会导致结果不确定 -
ignore_nulls参数仅控制 NULL 是否参与“最后”的判定,不解决帧定义问题
想取整行数据?别硬套 LAST_VALUE
LAST_VALUE 是单列函数,强行用多个 LAST_VALUE 分别取 id、name、status,一旦排序键不唯一,各字段可能来自不同记录,造成数据错位。
- 真正要“获取每组最新一条完整记录”,必须用
ROW_NUMBER()配合过滤 - 正确写法:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY updated_at DESC, id DESC),再外层WHERE rn = 1 - 用
FIRST_VALUE+ 降序排序也能绕过帧陷阱,但依然只能取单列,不能替代整行筛选 - 某些旧版 MySQL(8.0 前)根本不支持窗口函数,
LAST_VALUE直接报错
比 LAST_VALUE 更稳的替代写法
在 PostgreSQL、SQL Server、MySQL 8.0+、Oracle 中,以下写法更直观、易调试、索引友好:
SELECT user_id, order_id, status, updated_at
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY updated_at DESC, id DESC
) AS rn
FROM order_log
) t
WHERE rn = 1;
- 复合索引
(user_id, updated_at, id)能极大加速该查询 - 时间相同时靠
id DESC保证结果稳定,避免非确定性 - WHERE 条件必须在外层或 CTE 中写,不能塞进窗口子句——窗口计算发生在 WHERE 之前
- 别用
RANK()或DENSE_RANK(),它们会在并列时都标为 1,导致多行返回
真正容易被忽略的点是:窗口帧不是可选项,而是默认就错;而“取整行”这个需求,从一开始就不在 LAST_VALUE 的能力范围内。










