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,而 LAST_VALUE 在这个范围内“最后”就是当前行。这不是函数写错了,是窗口范围没显式改。
必须用 ROWS 显式定义边界,且要把右边界设为 UNBOUNDED FOLLOWING,否则它看不到后面的数据。
- 错误写法:
OVER (ORDER BY ts)—— 隐式框架,LAST_VALUE失效 - 正确写法:
OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) - 若想取分组内最后一个值(按时间):
OVER (PARTITION BY group_id ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
如何让 LAST_VALUE 返回分区中真正的“末尾值”?
关键不是“找最后一行”,而是把窗口滑到那行所在位置再取值。所以得先确保排序逻辑能定位到目标行(比如按时间倒序),再用 ROWS 框定范围。
例如:查每个用户最新一条订单的金额,并在每条订单记录里都填上这个最新金额。
SELECT
user_id,
order_time,
amount,
LAST_VALUE(amount) OVER (
PARTITION BY user_id
ORDER BY order_time DESC
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
) AS latest_amount
FROM orders;
注意:这里 ORDER BY order_time DESC 是为了让“第一个”物理行就是最新订单,LAST_VALUE 才能在该窗口中稳定取到它。
NULL 值和 ORDER BY 中重复值会怎样?
如果 ORDER BY 字段有重复(比如多个订单同秒下单),数据库会按任意顺序排列这些行,导致 LAST_VALUE 结果不可预测 —— 即使加了 ROWS,窗口内相对顺序仍不稳。
- 解决办法:在
ORDER BY后追加唯一列,如ORDER BY order_time DESC, order_id DESC - 如果值本身是
NULL,LAST_VALUE默认跳过(取决于 DBMS 实现),PostgreSQL 和 SQL Server 支持IGNORE NULLS,但 MySQL 8.0 不支持,需提前用COALESCE或子查询补值 - Oracle 的
LAST_VALUE默认含RESPECT NULLS,不加修饰时遇到NULL会停在上一个非空值上
替代方案:比 LAST_VALUE 更稳的写法有哪些?
当业务只要“分区最大/最新那一行的某个字段”,用 LAST_VALUE 容易绕进窗口细节里。更直接的做法是:
- 用
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ... DESC)标记最新行,再LEFT JOIN回去 - 用关联子查询:
(SELECT amount FROM orders o2 WHERE o2.user_id = o1.user_id ORDER BY order_time DESC LIMIT 1)(MySQL/PostgreSQL) - 在支持
FIRST_VALUE的场景下,把排序反过来,用FIRST_VALUE(amount) OVER (PARTITION BY user_id ORDER BY order_time DESC ROWS UNBOUNDED PRECEDING),语义更直白
真正需要 LAST_VALUE 的,往往是动态滑动场景(比如“从当前行起,往后看3条里的最高价”),而不是静态取“整个分区末尾”。这时候窗口范围才真正成为核心变量。










