首页/数据/SQL 速查/每组取最新一行:窗口函数写法

每组取最新一行:窗口函数写法

“每个用户最近一笔订单”这类需求的通用解法:ROW_NUMBER 按组内排序编号,取 rn=1 后左连接到主表。

SQL 速查数据更新于 2026-10-13

每组取最新一行是什么

“每个用户最近一笔订单”这类需求的通用解法:先用 ROW_NUMBER 按组内排序编号,取出每组编号为 1 的行,再左连接到主表,从而保留没有记录的主体。它比相关子查询更快,比 GROUP BY 更能保留整行信息。

基本信息

思路PARTITION BY 分组键,ORDER BY 时间倒序,外层取 rn = 1
连接方式LEFT JOIN 保留没有记录的主表行
索引分组键加排序列的复合索引可减少排序代价
排序键业务时间加主键,避免同值行顺序不确定
替代写法相关子查询取 MAX,或分组取最大值后再关联
适用场景需要输出主体全量清单并附带最近一次业务信息

每个用户最近一单

每个用户最近一单
WITH ranked AS (
    SELECT o.*,
           ROW_NUMBER() OVER (PARTITION BY o.user_id       -- 分组键
                              ORDER BY o.created_at DESC,  -- 最近优先
                                       o.id DESC)          -- 次级键保证顺序确定
             AS rn
    FROM orders o
)
SELECT u.id, u.name, r.amount AS last_amount
FROM users u
LEFT JOIN ranked r
       ON r.user_id = u.id AND r.rn = 1   -- 只连最近那一笔
ORDER BY u.id;   -- 没有订单的用户也会出现,金额为 NULL

几种写法取舍

写法优点代价
窗口函数取 1保留整行,只扫一遍表需要排序,占用排序内存
相关子查询取 MAX逻辑直观主表每行都要扫一遍子表
分组取 MAX 后关联聚合后再连,行数可控关联键值可能重复,需确认唯一性
左连接保留主体0 记录的对象也能出现未匹配行为 NULL,需要兜底处理

典型使用场景

  • 每个用户最近一笔订单或最近一次登录
  • 每个商品最新的价格记录
  • 每台设备最后一条上报数据
  • 客户最近一次跟进记录,用于销售看板
  • 取每个会话的最后一条消息
  • 补齐主体全量清单,同时带上最近一次业务信息
连接条件里的 r.rn = 1 不能漏,位置也不能挪:写在 ON 里表示“只连最近那一行”,写到 WHERE 就会把未匹配用户的 NULL 行过滤掉,LEFT JOIN 悄悄退化成 INNER JOIN。
排序键只有时间时,同一秒写入的多行谁算“最新”并不确定。补一列自增主键作为次级排序键,结果才可复现。

易错点

  • 漏写 r.rn = 1:会连出每个用户的全部订单行,行数成倍放大
  • 把 rn = 1 写进 WHERE:未匹配用户被过滤,LEFT JOIN 变成 INNER JOIN,0 订单用户从清单里消失
  • 排序键不够唯一:同一时间写入的记录每次取到的行可能不同,需要补主键排序
  • 先过滤再编号的顺序搞错:过滤掉的行不参与优先级判断,结果与业务预期相反

数据来源与参考

  • MySQL 8.0 Reference Manual · Window Functions
  • PostgreSQL Documentation · Window Functions
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌