last_value默认取不到分组内最后一条记录,因其窗口帧为rows between unbounded preceding and current row;需显式指定rows between unbounded preceding and unbounded following,并注意排序方向、null处理及数据库兼容性,取整行推荐用row_number()。

LAST_VALUE 为什么默认拿不到分组内“最后一条”
直接写 LAST_VALUE(col) OVER (PARTITION BY group_col ORDER BY time_col) 很可能返回当前行的值,而不是你期望的“每组最新那条记录的值”。这是因为 LAST_VALUE 默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW —— 它只看从开头到当前行,所以当前行就是“最后”,自然取不到真正的末尾。
解决办法是显式指定窗口范围:
- 用
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING让它看到整组所有行 - 确保
ORDER BY的排序方向和你要的“最后”逻辑一致(比如按时间升序排列时,“最后”是最大时间;若按降序排,则“最后”是第一个) - 如果想取整行(不止一个字段),
LAST_VALUE不够用,得配合ROW_NUMBER()或FIRST_VALUE变通
正确写法:LAST_VALUE + 显式窗口帧
假设表 orders 有字段 user_id、order_time、amount,要取每个用户最新一笔订单的 amount:
SELECT
user_id,
order_time,
amount,
LAST_VALUE(amount) OVER (
PARTITION BY user_id
ORDER BY order_time
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS latest_amount
FROM orders;
注意:ORDER BY order_time 是升序,所以“最后”对应最大时间;如果数据里 order_time 有重复,结果可能不确定,建议加二级排序(如 ORDER BY order_time, id)。
想取整条最新记录?LAST_VALUE 不是最佳选择
LAST_VALUE 只能返回单个表达式的值,不能“带出整行”。常见误操作是套多个 LAST_VALUE 去取不同字段,但一旦排序字段有重复或 NULL,各字段可能来自不同行,造成数据错位。
更可靠的做法是用 ROW_NUMBER():
SELECT user_id, order_time, amount
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY order_time DESC, id DESC
) AS rn
FROM orders
) t
WHERE rn = 1;
这个方案明确、可控,且兼容所有 SQL 标准实现(PostgreSQL / SQL Server / Oracle / DuckDB 都支持),而 LAST_VALUE 在 SQLite 中不支持窗口帧子句,MySQL 8.0+ 才完整支持。
性能和 NULL 处理容易被忽略
当分组内存在 NULL 的排序字段(如 order_time IS NULL),不同数据库对 NULLS FIRST/LAST 的默认行为不一致。PostgreSQL 默认 NULLS LAST,MySQL 和 SQL Server 默认把 NULL 当最小值处理 —— 这会导致 LAST_VALUE 实际取到的是 NULL 行而非你想要的时间最大行。
稳妥做法:
- 在
ORDER BY后加NULLS LAST(PostgreSQL/Oracle 支持)或IS NOT NULL DESC, order_time DESC(通用写法) - 确认目标数据库是否真正需要
LAST_VALUE:多数场景下,ROW_NUMBER()+ 子查询更直观、更少歧义、更容易加索引优化
窗口函数看着高级,但真要“取分组最新一行”,别硬套 LAST_VALUE —— 它的设计本意是做行内聚合参考,不是替代 Top-N 逻辑。










