LAST_VALUE默认返回当前行值,因窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW;需显式指定UNBOUNDED FOLLOWING才能获取组内末值,或改用FIRST_VALUE配合降序更稳妥。

LAST_VALUE 为什么总返回当前行的值
因为默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,不是整组——它只看“从开头到当前行”,所以 LAST_VALUE 在每一行都取自己,根本看不到后面的数据。你写 LAST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at),结果每行 amount 都原样照搬,不是“最新一笔”,而是“当前这一笔”。
显式指定窗口帧才能拿到真末值
必须补上 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,否则没用:
SELECT
user_id,
created_at,
amount,
LAST_VALUE(amount) OVER (
PARTITION BY user_id
ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS latest_amount
FROM orders;
-
ORDER BY created_at升序时,“最后”对应最大时间;若用降序,得同步调整语义理解 - 如果
created_at有重复,结果不确定——必须加二级排序,如ORDER BY created_at, id -
IGNORE NULLS可选,但仅影响 NULL 值跳过逻辑,不解决帧范围问题
想取整行?别硬套 LAST_VALUE
LAST_VALUE 只能逐列取值,不能“带出整条记录”。你写多个 LAST_VALUE(name)、LAST_VALUE(status),它们可能来自不同行——尤其当排序键不唯一时,字段错位风险极高。
真正要拿最新那条完整记录,用 ROW_NUMBER() 更稳:
SELECT user_id, order_id, status, created_at
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY created_at DESC, id DESC
) AS rn
FROM orders
) t
WHERE rn = 1;
- 所有主流引擎(PostgreSQL / MySQL 8.0+ / SQL Server / Oracle)都支持
- 复合索引
(user_id, created_at, id)能直接走索引,性能更好 - 过滤条件(如
status = 'done')必须放外层或 CTE,不能塞进窗口定义里
FIRST_VALUE + 降序比 LAST_VALUE 更少出错
多数场景下,“最新”=“按时间降序排的第一条”,所以用 FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY created_at DESC) 更自然。它默认帧就覆盖整组,不用手动补 ROWS 子句,也不容易漏掉。
但注意:这仍只返回单个字段值。如果要整行,还是得回到 ROW_NUMBER() 路线。
最易被忽略的一点:窗口函数计算发生在 WHERE 和 GROUP BY 之后,但早于 SELECT 列计算——这意味着你在 OVER 里写的排序和分区,是基于已过滤/聚合后的结果集。别指望它能“先筛选再窗口”,那是 CTE 或子查询的事。











