ROW_NUMBER()是最可靠方案,因它通过PARTITION BY分组、ORDER BY排序后编号,再筛选rn=1可精确获取每组首行完整记录;而MAX()+GROUP BY仅返回最大值,丢失整行字段。

为什么ROW_NUMBER()是分组取首行最可靠的选择
因为GROUP BY本身不保留原始行顺序,也没法直接返回“每组第一条”,而ROW_NUMBER()能按指定排序稳定编号,且支持PARTITION BY实现分组内独立计数。其他方式如MIN()/MAX()只能取字段值,无法带回整行数据;子查询+LIMIT 1在标准SQL中不支持分组级限制,且MySQL 8.0前不支持窗口函数,但如今已是主流解法。
ROW_NUMBER()分组取首行的标准写法
核心是两步:先用子查询或CTE生成带序号的临时结果,再过滤序号为1的行。注意ORDER BY必须明确——否则“第一行”无定义,不同执行可能返回不同结果。
常见写法:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn
FROM products
) t WHERE rn = 1;-
PARTITION BY category表示按category分组编号 -
ORDER BY created_at DESC决定谁是“第一行”,这里取最新创建的 - 别名
rn必须在外部查询中引用,不能直接在窗口函数所在层用WHERE rn = 1 - 如果想取最早创建的,把
DESC换成ASC即可
遇到NULL值或排序字段重复时怎么办
ROW_NUMBER()会强制给每行唯一编号,哪怕ORDER BY字段相同。这和RANK()、DENSE_RANK()不同——后两者对相同值并列编号,会导致多个“第1名”。所以只要目标是“严格取一行”,就该坚持用ROW_NUMBER()。
但若排序字段含NULL,默认NULLS FIRST(多数数据库)可能导致意外排第一。稳妥做法是显式控制:
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC NULLS LAST)
- 加
NULLS LAST确保NULL不挤占“第一行”位置 - 若业务允许用
COALESCE(salary, 0)替代NULL,也可避免歧义 - 多字段排序更稳,例如
ORDER BY status DESC, updated_at DESC,可降低并列概率
性能和兼容性要注意什么
窗口函数在PostgreSQL 8.4+、SQL Server 2005+、Oracle 8i+、MySQL 8.0+、SQLite 3.25+均支持,但旧版本MySQL(5.7及以前)不支持,此时只能用关联子查询或变量模拟,复杂度高且不可靠。
性能上,PARTITION BY字段最好有索引,尤其当表很大时。如果只查少量分组(比如WHERE category IN ('A','B')),建议把过滤条件放在子查询外层,避免全表打序号:
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY id DESC) AS rn
FROM products
WHERE category IN ('electronics', 'books') -- 先过滤,再排序编号
) t WHERE rn = 1;真正容易被忽略的是:没有ORDER BY的ROW_NUMBER()行为未定义,某些数据库可能按物理存储顺序返回,但绝不应依赖——上线后数据迁移或优化器升级就可能出问题。

















