SQL 如何实现“每组取最新一条”?

12次阅读

用ROW_NUMBER()配合子查询或CTE取每组最新记录最常用可靠:按user_id分组、order_time倒序编号,取rn=1;需处理NULL(如加NULLS LAST)和并列(改用RANK);老版本可用关联子查询但性能差;禁用GROUP BY混选非聚合字段。

SQL 如何实现“每组取最新一条”?

用窗口函数 ROW_NUMBER() 配合子查询或 CTE 是最常用、最可靠的方法。

用 ROW_NUMBER() 按组排序取 Top 1

核心思路:给每组数据按时间(或 ID)倒序编号,取编号为 1 的记录。

假设表 orders 包含字段 user_idorder_timeamount,要查每个用户最新的一笔订单:

select user_id, order_time, amount FROM (   SELECT *,          ROW_NUMBER() OVER (            PARTITION BY user_id             ORDER BY order_time DESC          ) AS rn   FROM orders ) t WHERE rn = 1;

注意 NULL 和并列情况

如果 order_time 可能为 NULL,需显式处理,否则这些行可能被排在最前或最后(取决于数据库默认行为):

  • NULLS LASTpostgresqloracle、SQL Server 2022+ 支持)确保 NULL 排末尾
  • mysql 8.0+ 也支持 NULLS LAST;旧版可用 IFNULL(order_time, ‘1970-01-01’) 临时兜底
  • 若同一用户有多条相同最新时间的订单,ROW_NUMBER() 会任意选一条;要全取则改用 RANK()DENSE_RANK(),再把 rn = 1 改成 rn = 1 且保留所有并列项

替代方案:关联子查询(兼容老版本)

不支持窗口函数的老数据库(如 MySQL 5.7)可用相关子查询:

SELECT o1.user_id, o1.order_time, o1.amount FROM orders o1 WHERE o1.order_time = (   SELECT MAX(o2.order_time)   FROM orders o2   WHERE o2.user_id = o1.user_id );

⚠️ 注意:这个写法在有重复最大时间时会返回多条,且性能通常不如窗口函数,尤其数据量大时。

避免常见错误

  • 别用 GROUP BY user_id + 非聚合字段(如 SELECT user_id, order_time, amount FROM orders GROUP BY user_id),结果不可靠,MySQL 5.7+ 默认拒绝,其他库行为未定义
  • 别依赖 ORDER BY … LIMIT 1 在子查询里直接套,无法按组生效
  • 时间字段类型要一致(比如别混用 DATETIMEtimestamp),否则排序可能出偏差

text=ZqhQzanResources