ROW_NUMBER()本身不直接去重,但通过PARTITION BY分组、ORDER BY排序(如created_at DESC)、外层WHERE rn=1筛选,可精准实现“每组留一条”;其严格唯一编号特性优于RANK()/DENSE_RANK(),避免并列导致多行保留。

ROW_NUMBER() 本身不直接去重,但能帮你标记重复行
很多人以为 ROW_NUMBER() 是个去重函数,其实它只是给每一行按排序规则打序号。真正去重得靠它配合 WHERE 或子查询筛选——比如只保留每组中序号为 1 的那行。
典型用法:按某几列分组取第一条
假设你有一张订单表 orders,字段有 user_id、product_id、created_at,想对每个 user_id + product_id 组合只留最新的一条记录:
SELECT user_id, product_id, created_at
FROM (
SELECT user_id, product_id, created_at,
ROW_NUMBER() OVER (
PARTITION BY user_id, product_id
ORDER BY created_at DESC
) AS rn
FROM orders
) t
WHERE rn = 1;
-
PARTITION BY决定“哪些行算一组”,这里按用户+商品分组 -
ORDER BY ... DESC确保最新时间排第一,rn = 1就拿到最新记录 - 如果改用
ASC,就变成取最早一条 - 注意:
ROW_NUMBER()不处理 NULL 值的分组逻辑,含 NULL 的user_id或product_id会被单独归为一组
和 DISTINCT、GROUP BY 的关键区别在哪?
DISTINCT 只能返回原始字段的唯一组合,不能选“每组里某个特定行”;GROUP BY 必须配聚合函数,没法直接保留非聚合字段的完整行数据。而 ROW_NUMBER() 方案能完整保留你想保留的那行所有字段。
- 如果你只需要去重后的
user_id列,用SELECT DISTINCT user_id FROM orders更快更简单 - 但如果你要的是“每个用户最新下单的完整订单信息”,
ROW_NUMBER()是更可控的选择 - 性能上,
ROW_NUMBER()在大数据量时会触发窗口计算,比DISTINCT开销大,别在没索引的PARTITION BY字段上乱用
容易踩的坑:ORDER BY 为空或含不确定值
如果 ORDER BY 子句缺失,或者排序字段存在大量重复值(比如多个订单同秒创建),ROW_NUMBER() 的结果是不确定的——同一语句多次执行可能选出不同行。
- 务必显式写全
ORDER BY,且优先选能唯一区分行的字段(如主键id)作为次级排序条件 - 例如:
ORDER BY created_at DESC, id DESC可避免因时间相同导致随机取值 - MySQL 8.0+、PostgreSQL、SQL Server 都支持,但 SQLite 3.25+ 才支持窗口函数,老版本会报错
no such function: ROW_NUMBER
真正麻烦的不是写法,而是想清楚“你定义的‘重复’到底指什么”——字段组合?业务含义?时间先后?这些决定了 PARTITION BY 和 ORDER BY 怎么写,而不是抄个模板就完事。

















