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