ROW_NUMBER() 仅编号不自动去重,须配合子查询或CTE筛选rn=1或rn>1;核心是PARTITION BY指定重复判定字段,ORDER BY控制组内排序;误用ORDER BY全表编号将无法识别重复。

用 ROW_NUMBER() 给重复行打序号,再筛出重复组
直接结论:不能靠 ROW_NUMBER() 自己“去重”,它只编号;真正去重要配合子查询或 CTE,把编号为 1(或 >1)的行挑出来。核心是先定义“按哪些字段判重复”,再用 PARTITION BY 分组编号。
常见错误是写成 ROW_NUMBER() OVER (ORDER BY col) —— 这会全表编号,根本没分组,看不出谁和谁重复。
- 必须用
PARTITION BY col1, col2指定重复判定字段(比如email、user_id, order_date) -
ORDER BY在括号内只控制组内排序逻辑,常用id或created_at确保稳定(避免每次执行结果不同) - 编号从 1 开始,每组独立计数,所以重复数据必然有
rn = 1和rn > 1的行
查出所有重复记录(含编号),方便人工核对
这是最常被跳过的一步:不先看清楚重复长什么样,就急着删,容易误伤。用 CTE 把编号结果拎出来,再加个 COUNT(*) OVER (PARTITION BY ...) 能直接看到每组几条。
WITH duped AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn,
COUNT(*) OVER (PARTITION BY email) AS cnt
FROM users
)
SELECT * FROM duped WHERE cnt > 1;注意:cnt > 1 是筛选“属于重复组”的所有行;rn = 1 才是每组保留的那条。别混淆这两个条件。
保留每组第一条,删掉其余重复行(安全删除写法)
直接 DELETE 风险高,务必先备份或在事务里试。关键是让数据库知道“哪些行该删”——靠子查询关联原表主键,而不是用 ROW_NUMBER() 结果直接删(某些数据库不支持)。
- PostgreSQL / SQL Server / Oracle 支持
DELETE ... USING或DELETE FROM ... WHERE id IN (SELECT ...) - MySQL 8.0+ 可用 CTE +
DELETE,但老版本需用临时表或自连接 - 示例(通用写法):
DELETE FROM users WHERE id NOT IN ( SELECT MIN(id) FROM users GROUP BY email );
这比ROW_NUMBER()更简洁,但无法控制“保留哪一条”(只能靠MIN/MAX)
ROW_NUMBER() 和 DENSE_RANK() 在去重场景的区别
有人试过 DENSE_RANK() 替代,结果发现编号没用——因为去重逻辑依赖的是“组内唯一序号是否大于 1”,而 DENSE_RANK() 对相同值给相同排名,无法区分同组内的不同行。
-
ROW_NUMBER():组内严格 1, 2, 3… → 可精准定位“第 1 条”和“非第 1 条” -
RANK()或DENSE_RANK():遇到相同ORDER BY值会并列,比如 1, 1, 3 或 1, 1, 2 → 无法用rn = 1安全保留唯一行 - 性能上无明显差异,但语义清晰度差很多,别图省事换函数
真正麻烦的不是写法,而是“重复”的定义本身是否合理——比如忽略空格、大小写、时区偏移的邮箱算不算重复?这些得在 PARTITION BY 之前用 TRIM(LOWER(email)) 处理,否则编号分组就错了。

















