LAST_VALUE不能直接获取每组最后一条完整记录,因其是窗口函数,仅返回当前帧内最后一个值;正确做法是用ROW_NUMBER()加子查询筛选rn=1的行,或SQL Server 2022+用OUTER APPLY配合TOP 1。

LAST_VALUE 函数在 SQL Server 中不能直接“获取每组最后一条记录”
很多人看到 LAST_VALUE 就默认它能像 MAX() 那样聚合出每组的“最后一条”,但事实不是这样。LAST_VALUE 是窗口函数,它只在当前窗口帧(frame)内找最后一个值,**不等于按某列排序后取每组末尾那行的完整记录**。直接用它返回多列、带 ID 或时间戳的整行数据,大概率出错或结果不符合预期。
为什么 LAST_VALUE 常常返回错误结果
典型陷阱是没设对 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING —— 默认窗口帧是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致 LAST_VALUE 实际返回的是“从开头到当前行”的最后一个值,而不是整组的最后一个。
- 错误写法:
LAST_VALUE(id) OVER (PARTITION BY category ORDER BY created_time)→ 每行都可能得到不同值,且不是该组真正的最后id - 正确帧声明:
LAST_VALUE(id) OVER (PARTITION BY category ORDER BY created_time ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 但即使帧对了,
LAST_VALUE仍只能返回单个表达式的值(比如只返回id),无法带回name、created_time等其他字段
真正可靠的做法:用 ROW_NUMBER() + 子查询
这是 SQL Server 中最通用、可读性强、兼容性好(支持 2005+)的方案。核心思路是:先按分组和排序生成序号,再筛选序号为 1 的行(倒序排时,1 就是“最后”)。
SELECT id, name, category, created_time
FROM (
SELECT id, name, category, created_time,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY created_time DESC, id DESC
) AS rn
FROM orders
) t
WHERE rn = 1;
-
ORDER BY created_time DESC, id DESC解决时间相同时的不确定性(比如并发插入) - 如果业务上允许任意一条“最后”,可只按
created_time DESC;若必须稳定,建议补一个唯一列(如id)做二级排序 - 注意:不要用
RANK()或DENSE_RANK(),它们会把并列的都标为 1,导致返回多行
SQL Server 2022+ 可用 LATERAL(APPLY)简化逻辑
如果你用的是 SQL Server 2022 或 Azure SQL DB,可以用 OUTER APPLY 配合 TOP 1,语义更清晰,也更容易加复杂条件:
SELECT o1.category, o2.id, o2.name, o2.created_time FROM (SELECT DISTINCT category FROM orders) o1 OUTER APPLY ( SELECT TOP 1 id, name, created_time FROM orders o2 WHERE o2.category = o1.category ORDER BY created_time DESC, id DESC ) o2;
- 避免了窗口函数的帧理解门槛
- 子查询中可自由加
WHERE过滤(比如只取 status = 'completed' 的最后一条) - 性能取决于
category + created_time是否有合适索引;没有的话,ROW_NUMBER()方案通常更稳
真正要拿“每组最后一条完整记录”,别硬套 LAST_VALUE。它设计初衷是做横向比较(比如“本组最后成交价 vs 当前行价格”),不是做行级筛选。窗口帧、排序稳定性、字段完整性这三点,漏掉任何一个都容易掉坑里。

















