LAST_VALUE常返回“错的最后一条”是因为默认窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,仅覆盖当前行及之前行,导致实际返回当前行值;必须显式指定ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,并配合确定性ORDER BY(如create_time DESC)才能正确取分组末值。

LAST_VALUE 窗口函数为什么经常返回“错的最后一条”?
直接用 LAST_VALUE 却拿不到分组内时间/序号最大的那条记录,十有八九是没配对 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING。默认框架是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致它只看“当前行及之前”,LAST_VALUE 实际返回的是当前行——不是分组末尾,而是当前位置。
- 必须显式指定窗口帧:加
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -
ORDER BY的字段决定“最后”的语义(比如按create_time DESC,则“最后”=最早创建;按id ASC才是最大ID) - 若需“每组最新一条”,
ORDER BY通常要配合DESC+ 时间戳,再搭配ROWS全局帧
如何用 LAST_VALUE 拿到分组中 create_time 最大的那条完整记录?
仅靠 LAST_VALUE 只能取单个字段值,无法直接返回整行。常见做法是:先用 LAST_VALUE 标记每组“目标时间”,再用子查询或 JOIN 关联回原表匹配。更稳妥的替代方案其实是 ROW_NUMBER() 配合 WHERE rn = 1。
- 用
LAST_VALUE(create_time) OVER (PARTITION BY category ORDER BY create_time DESC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)得到每组最大时间 - 但该值需和原表
create_time精确相等才能关联,存在重复时间时可能命中多条——这时ROW_NUMBER()更可控 - 若业务允许“任一最新记录”,可改用
FIRST_VALUE(*)配合ORDER BY create_time DESC,但注意:SQL 标准不支持FIRST_VALUE(*),得逐个列写
替代方案:为什么 ROW_NUMBER() OVER (...) = 1 更常用且可靠?
绝大多数场景下,想取分组后“最后一条记录”,真实意图是“每组按某字段排序后的第一条(即 top 1)”。ROW_NUMBER() 是最直白、兼容性最好、语义最清晰的选择。
- 写法简单:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY update_time DESC, id DESC) AS rn - 重复时间时,加二级排序(如
id DESC)确保结果确定 - 主流数据库(PostgreSQL、SQL Server、MySQL 8.0+、Oracle)全支持,无帧定义陷阱
- 性能上,只要
PARTITION BY和ORDER BY字段有索引,开窗成本可控
MySQL 5.7 或 SQLite 等不支持窗口函数怎么办?
没有 LAST_VALUE 或 ROW_NUMBER 时,只能靠自连接或相关子查询模拟。核心思路是:对每条记录,统计同组中“排序字段更大”的记录数为 0。
- 例如取每用户最新订单:
SELECT o1.* FROM orders o1 WHERE NOT EXISTS (SELECT 1 FROM orders o2 WHERE o2.user_id = o1.user_id AND o2.create_time > o1.create_time) - 注意:若
create_time可能为 NULL,需额外加o2.create_time IS NOT NULL条件 - 该写法在大数据量时易慢,务必确保
(user_id, create_time)有联合索引 - SQLite 3.25+ 支持窗口函数,但旧版仍需上述方式
真正棘手的不是语法怎么写,而是想清楚“最后一条”的业务定义:是时间最晚?ID 最大?还是插入顺序最后?不同定义对应不同的 ORDER BY 方向和去重策略。一旦排序依据本身不唯一,又没加二级键兜底,任何窗口函数都可能返回非预期结果。










