每组取最新一行是什么
“每个用户最近一笔订单”这类需求的通用解法:先用 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。
排序键只有时间时,同一秒写入的多行谁算“最新”并不确定。补一列自增主键作为次级排序键,结果才可复现。