LAST_VALUE默认只取当前行及之前数据,需显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING才能取末条;更推荐用FIRST_VALUE配合逆序排序或ROW_NUMBER()获取整行。

LAST_VALUE 默认行为常导致“取到第一条”而非末条
直接写 LAST_VALUE(col) OVER (PARTITION BY group_col ORDER BY order_col) 很可能返回分组内第一条记录的值,不是末条。这是因为窗口帧默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW —— 也就是只看当前行及之前,LAST_VALUE 在这个范围内自然只能取到“当前行”,根本没机会看到后面的数据。
必须显式指定窗口帧范围才能让它看到整组:
- 用
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING让它扫完整个分组 - 确保
ORDER BY的排序逻辑和你定义的“末条”一致(比如按时间倒序时,“末条”是最早的时间;按时间正序时,“末条”是最新时间) - 如果排序字段有重复值,
LAST_VALUE可能不稳定,建议补一个唯一字段(如主键)做二级排序:ORDER BY ts ASC, id ASC
替代方案:用 FIRST_VALUE 配合逆序更可靠
比起硬调 LAST_VALUE,多数场景下改用 FIRST_VALUE + 反向排序更直观、不易出错。例如想取每个用户最新一条订单:
SELECT
user_id,
FIRST_VALUE(order_id) OVER (
PARTITION BY user_id
ORDER BY created_at DESC, id DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS latest_order_id
FROM orders;
这样逻辑清晰:“在每组里,按时间倒序排,取第一个”。不用纠结帧范围是否写对,也不用担心 LAST_VALUE 在某些数据库(如旧版 PostgreSQL)中对空值或重复排序的处理差异。
- MySQL 8.0+、PostgreSQL、SQL Server、Oracle 均支持该写法
- SQLite 目前不支持窗口函数的
ROWS子句,无法用此法(需改用子查询或ROW_NUMBER()) - 如果只要某几列,别忘了
FIRST_VALUE是逐列计算的,不能直接套整个行
真正要取“末条整行”时,别硬扛 LAST_VALUE
LAST_VALUE 是标量函数,一次只能返回一列。想拿到末条记录的所有字段(比如 id, name, amount, created_at),强行用多个 LAST_VALUE 不仅冗长,还可能因排序不稳定导致各列来自不同行。
更稳妥的做法是用 ROW_NUMBER():
SELECT id, name, amount, 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;
- 语义明确:给每组按顺序编号,取编号为 1 的那行
- 兼容性好:所有支持窗口函数的主流 SQL 引擎都支持
ROW_NUMBER() - 避免隐式帧问题:不依赖
LAST_VALUE的帧定义细节,也不受RESPECT NULLS等可选参数干扰
注意数据库差异:Oracle 和旧版 PostgreSQL 的 LAST_VALUE 行为不同
Oracle 默认 LAST_VALUE 会自动使用 GROUPS 或全范围帧,而 PostgreSQL 13 以前版本即使写了 ORDER BY 也默认用 CURRENT ROW 帧 —— 这意味着同样 SQL 在两个库上结果可能完全相反。
- 显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING是跨库安全的底线 - PostgreSQL 14+ 开始允许省略帧子句时按标准行为推断,但生产环境仍建议写全
- SQL Server 中
LAST_VALUE必须配帧,否则报错,反而不容易踩坑
真要用 LAST_VALUE,就别省那几个字符。窗口函数的默认行为太容易被低估了。










