如何通过ROW_NUMBER函数在SQL中实现按分类统计最新记录?
- 内容介绍
- 相关推荐
本文共计573个文字,预计阅读时间需要3分钟。
直接输出结果:
为什么不能只用 GROUP BY + MAX(time)
常见误区是写 SELECT category, MAX(created_at) FROM t GROUP BY category,这只能拿到时间,拿不到整行数据(比如对应这条记录的 title 或 status)。想查完整记录,必须关联原表或改用窗口函数。用 ROW_NUMBER() 是最直白、兼容性好(MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持)的解法。
实际写法和容易踩的坑
典型结构如下:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY created_at DESC, id DESC ) AS rn FROM products ) t WHERE rn = 1;
注意这些细节:
-
PARTITION BY后面填分类字段,比如category、user_id,别漏掉括号 -
ORDER BY必须包含能唯一确定“最新”的字段;如果created_at可能重复,一定要加二级排序(如id DESC),否则ROW_NUMBER()的结果不稳定 - MySQL 5.7 或更早不支持窗口函数,强行运行会报错
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'类似提示,得换思路(比如用自连接或变量) - 如果表很大,确保
category和created_at上有联合索引,否则PARTITION BY + ORDER BY会全表扫描
替代方案对比:RANK() 和 DENSE_RANK() 为什么不行
这三个函数都做排序编号,但行为不同:ROW_NUMBER() 严格按顺序给唯一序号;RANK() 和 DENSE_RANK() 遇到并列会跳号或不跳号。比如两条记录时间相同,RANK() 会返回 1,1,3 —— 这时 WHERE rn = 1 会取到两条,不符合“每组只取最新一条”的需求。只有 ROW_NUMBER() 能保证结果确定且唯一。
真正麻烦的是时间字段为空、时区不一致、或“最新”其实取决于状态变更日志这类非主键时间字段——这时候光靠 ORDER BY 不够,得先清洗或补全时间值。
本文共计573个文字,预计阅读时间需要3分钟。
直接输出结果:
为什么不能只用 GROUP BY + MAX(time)
常见误区是写 SELECT category, MAX(created_at) FROM t GROUP BY category,这只能拿到时间,拿不到整行数据(比如对应这条记录的 title 或 status)。想查完整记录,必须关联原表或改用窗口函数。用 ROW_NUMBER() 是最直白、兼容性好(MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持)的解法。
实际写法和容易踩的坑
典型结构如下:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER ( PARTITION BY category ORDER BY created_at DESC, id DESC ) AS rn FROM products ) t WHERE rn = 1;
注意这些细节:
-
PARTITION BY后面填分类字段,比如category、user_id,别漏掉括号 -
ORDER BY必须包含能唯一确定“最新”的字段;如果created_at可能重复,一定要加二级排序(如id DESC),否则ROW_NUMBER()的结果不稳定 - MySQL 5.7 或更早不支持窗口函数,强行运行会报错
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'类似提示,得换思路(比如用自连接或变量) - 如果表很大,确保
category和created_at上有联合索引,否则PARTITION BY + ORDER BY会全表扫描
替代方案对比:RANK() 和 DENSE_RANK() 为什么不行
这三个函数都做排序编号,但行为不同:ROW_NUMBER() 严格按顺序给唯一序号;RANK() 和 DENSE_RANK() 遇到并列会跳号或不跳号。比如两条记录时间相同,RANK() 会返回 1,1,3 —— 这时 WHERE rn = 1 会取到两条,不符合“每组只取最新一条”的需求。只有 ROW_NUMBER() 能保证结果确定且唯一。
真正麻烦的是时间字段为空、时区不一致、或“最新”其实取决于状态变更日志这类非主键时间字段——这时候光靠 ORDER BY 不够,得先清洗或补全时间值。

