必须配合PARTITION BY和ORDER BY使用,否则仅为全局编号;需用子查询包裹并外层WHERE rn=1筛选,ORDER BY决定“首条”定义,且PARTITION BY不等价于GROUP BY。

用 ROW_NUMBER() 实现“每组取第一条”的核心逻辑
直接结论:必须配合 OVER (PARTITION BY ... ORDER BY ...) 使用,否则 ROW_NUMBER() 只是全局编号,起不到分组作用。
关键在于 ORDER BY 子句——它决定“哪条算第一”。比如按时间倒序排,ROW_NUMBER() = 1 就是最新记录;按分数升序排,就是最低分那条。不写 ORDER BY 会报错,SQL 标准不允许无序编号。
常见错误现象:ROW_NUMBER() OVER (PARTITION BY user_id)(缺 ORDER BY)→ 直接语法错误。
嵌套子查询是唯一安全的写法
ROW_NUMBER() 是窗口函数,不能直接在 WHERE 中过滤行号,必须先把它算出来,再在外层筛选 rn = 1。
实操建议:
- 把带
ROW_NUMBER()的查询写成子查询或 CTE,别试图塞进WHERE或HAVING - 别用
SELECT * FROM (...) WHERE rn = 1后还加GROUP BY——这既冗余又可能报错(MySQL 5.7+ 严格模式下字段未聚合) - 如果只需部分字段,外层
SELECT显式列出,避免把rn暴露给业务代码
示例:
SELECT user_id, order_id, amount
FROM (
SELECT user_id, order_id, amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1;
注意 PARTITION BY 和 GROUP BY 的本质区别
PARTITION BY 不聚合数据,只是划分窗口范围;GROUP BY 会压缩行数并要求聚合函数。这是初学者最常混淆的点。
使用场景差异:
- 要保留原始行的所有字段(如取最新订单的完整信息),只能用
PARTITION BY+ROW_NUMBER() - 如果只需要每组统计值(如最大金额、平均值),用
GROUP BY更高效,别硬套窗口函数 -
PARTITION BY (a, b)等价于按多列分组,但不会像GROUP BY a, b那样自动去重——重复键值会各自形成窗口
性能和兼容性避坑点
窗口函数在 MySQL 8.0+、PostgreSQL、SQL Server 2005+、Oracle 8i+ 都支持,但 SQLite 要 3.25.0+,旧版 Hive/Spark SQL 可能需开启配置。
性能影响主要来自 ORDER BY:如果分区键上没索引,且数据量大,ORDER BY created_at DESC 可能触发临时文件排序。
容易被忽略的细节:
- NULL 值在
PARTITION BY中会被视为同一组(多数引擎如此),如果业务上 NULL 表示“未知用户”,可能意外合并多条记录 -
ROW_NUMBER()总是生成连续整数,即使ORDER BY字段有重复值;若需相同值同名次,改用RANK()或DENSE_RANK() - 某些场景下(如分页取每组前 N 条),
WHERE rn 比多次查rn = 1更省事










