row_number()不能直接去重,但通过partition by按业务字段分组、order by指定保留逻辑(如created_at desc)、外层where rn=1筛选,可精准实现“每组留一条”;其唯一序号特性优于rank()/dense_rank(),避免并列导致多行保留。

ROW_NUMBER() 不能直接去重,但它能帮你识别重复行——关键看你怎么用 PARTITION BY 和 ORDER BY。
为什么 ROW_NUMBER() 不等于 DISTINCT
很多人以为 ROW_NUMBER() 能像 DISTINCT 那样“去掉重复”,其实它只是给每行打序号。真正去重得靠后续筛选(比如只取序号为 1 的行)。如果漏掉 PARTITION BY,它会对全表编号,完全起不到分组去重效果。
- 没写
PARTITION BY→ 全表连续编号(1,2,3,…),和去重无关 -
PARTITION BY指定分组字段 → 每组内独立编号(每组都从 1 开始) -
ORDER BY决定组内排序逻辑 → 直接影响哪一行拿到1(即你保留的“代表行”)
按业务字段去重并保留最新记录
常见场景:用户表有重复 user_id,要保留 created_at 最大的那条。这时候 ORDER BY created_at DESC 是关键,否则可能留下最旧数据。
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY user_id
ORDER BY created_at DESC, id DESC
) AS rn
FROM users
) t WHERE rn = 1;
-
PARTITION BY user_id:把相同用户归为一组 -
ORDER BY created_at DESC, id DESC:时间相同就用主键保序,避免非确定性结果 - 外层
WHERE rn = 1:每组只留“最优”一条
和 RANK()、DENSE_RANK() 的区别在哪
三者都会编号,但遇到相同排序值时行为不同——这直接影响你能否准确选出“唯一代表行”。
-
ROW_NUMBER():严格不重复,1,2,3… 即使ORDER BY值相同也强行分先后 -
RANK():相同值同名次,跳号(如 1,1,3)→ 可能多个rn = 1,导致去重失败 -
DENSE_RANK():相同值同名次,不跳号(如 1,1,2)→ 同样可能多条=1
所以只要目标是“每组只留一条”,必须用 ROW_NUMBER(),且 ORDER BY 末尾加一个唯一字段(如主键)来打破并列。
性能和兼容性要注意什么
ROW_NUMBER() 是标准 SQL:2003 特性,主流数据库都支持,但 MySQL 8.0+、PostgreSQL、SQL Server、Oracle 没问题;SQLite 3.25+ 才支持,旧版会报错 no such function: ROW_NUMBER。
- 大数据量时,
PARTITION BY字段最好有索引,尤其配合ORDER BY使用 - 别在子查询里反复调用
ROW_NUMBER(),先算好再 JOIN,否则执行计划容易变慢 - 某些 OLAP 场景下,
ROW_NUMBER() OVER (ORDER BY ...)(无 PARTITION)可能触发全局排序,比预期更耗资源
真正难的不是写对语法,而是想清楚:你要按什么分组?组内以什么逻辑决定“谁留下”?这个逻辑是否在所有边界情况下(时间相同、空值、NULL 排序)都稳定。











