用ROW_NUMBER()分组取首行需在OVER中正确写PARTITION BY和ORDER BY,外层筛选rn=1;ORDER BY错位或缺失会导致结果错误或不可靠;PARTITION BY字段须建索引,推荐复合索引(category, create_time DESC)。

ROW_NUMBER() 分组取首行的核心逻辑
直接结论:用 ROW_NUMBER() 配合 PARTITION BY 和 ORDER BY 生成序号,再在外层筛选 rn = 1。关键不是函数本身,而是窗口定义是否匹配业务意图——比如“第一条”究竟按什么排序?时间最新?金额最高?ID最小?没想清楚这点,结果必然错。
常见错误:ORDER BY 写错位置或漏写
典型现象是分组后序号全为 1,或者排序完全不生效。这是因为 ROW_NUMBER() 的 ORDER BY 必须写在窗口定义里(OVER 子句中),而不是放在外层 ORDER BY。漏掉 ORDER BY 则行为未定义,不同数据库可能返回任意一行。
-
ROW_NUMBER() OVER (PARTITION BY category ORDER BY create_time DESC)✅ 按时间倒序,取最新一条 -
ROW_NUMBER() OVER (PARTITION BY category) ORDER BY create_time DESC❌ 外层ORDER BY不影响窗口函数计算 -
ROW_NUMBER() OVER (PARTITION BY category)❌ 无ORDER BY,结果不可靠
性能注意:PARTITION BY 字段要建索引
当数据量大时,PARTITION BY 字段若无索引,会导致全表扫描+大量临时排序。尤其在 MySQL 8.0+、PostgreSQL、SQL Server 中,执行计划里容易看到 WindowAgg 或 Sort 成为瓶颈。
- 复合索引优先考虑:
(category, create_time DESC)(覆盖分区和排序) - 避免在
PARTITION BY中用函数或表达式,如PARTITION BY YEAR(order_date),会失效索引 - Oracle 中若用
ROW_NUMBER()+ 大偏移分页,注意OFFSET仍需跳过前 N 行,不是真正跳过计算
替代方案对比:ROW_NUMBER() vs. DISTINCT ON vs. Correlated Subquery
不是所有场景都该硬套 ROW_NUMBER()。比如 PostgreSQL 有更简洁的 DISTINCT ON;而某些老版本 MySQL(
- PostgreSQL 推荐:
SELECT DISTINCT ON (category) * FROM orders ORDER BY category, create_time DESC - MySQL 5.7 以下:
SELECT o1.* FROM orders o1 LEFT JOIN orders o2 ON o1.category = o2.category AND o1.create_time -
ROW_NUMBER()在 SQL Server/Oracle/MySQL 8.0+ 统一性好,但嵌套一层子查询,可读性略低
实际写法最简模板就是:
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY <code>group_col</code> ORDER BY <code>sort_col</code> DESC) AS rn FROM <code>table_name</code> ) t WHERE t.rn = 1;真正容易被忽略的,是业务上“第一条”的定义是否随时间变化——比如按
create_time 取最新,但该字段允许 NULL,那这些记录会被排到末尾还是开头?不同数据库处理 NULL 的默认顺序不同,必须显式写成 ORDER BY create_time DESC NULLS LAST(PostgreSQL/Oracle)或用 COALESCE 兜底。

















