ROW_NUMBER()不能直接删除数据,必须配合CTE或子查询先按PARTITION BY分组、ORDER BY确定保留规则(如id升序留最小值),再DELETE rn>1的行;执行前须SELECT预览验证。

用 ROW_NUMBER() 窗口函数识别并删除重复行
直接 DELETE 无法区分“哪一行该留、哪一行该删”,必须先标记重复项。主流方案是用 ROW_NUMBER() 给每组重复数据编号,再删掉编号 >1 的行。这个方法兼容 MySQL 8.0+、PostgreSQL、SQL Server、Oracle,但不适用于 SQLite 或旧版 MySQL。
假设表 users 中 email 字段重复,想保留 id 最小的那条:
DELETE FROM users
WHERE id NOT IN (
SELECT min_id FROM (
SELECT MIN(id) AS min_id
FROM users
GROUP BY email
) t
);或者更通用的窗口函数写法(推荐):
DELETE FROM users
WHERE id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
) t
WHERE rn > 1
);-
PARTITION BY email表示按邮箱分组,ORDER BY id决定谁排第一(即保留最小id) - 不同数据库对子查询中引用原表名的限制不同:MySQL 要求嵌套一层别名,PostgreSQL 允许直接用;执行前务必在测试环境验证
- 如果表没主键或唯一标识字段,得靠其他字段组合判断重复,比如
PARTITION BY name, phone, address
没有窗口函数时用自连接删除重复项
MySQL 5.7 或老版本 PostgreSQL 只能靠自连接 + 条件匹配来删。原理是让每条记录和同组其他记录比较,只删“更大”的那个。
还是以 email 为重复依据,保留 id 小的:
DELETE u1 FROM users u1 INNER JOIN users u2 WHERE u1.email = u2.email AND u1.id > u2.id;
- 注意
u1.id > u2.id是关键:它确保只删掉“比同组另一条记录 id 更大”的行 - MySQL 支持这种多表
DELETE语法,但 PostgreSQL 和 SQL Server 需改用USING或子查询,不能直接写DELETE u1 FROM ... - 数据量大时性能较差,因为要全表自连接;建议先加索引:
CREATE INDEX idx_email ON users(email);
误删风险高,必须提前备份或用事务包裹
删重复数据不是幂等操作——第二次运行可能把原本唯一的记录也删掉。最常见错误是没确认重复逻辑是否真覆盖所有情况,比如忽略大小写或空格差异。
- 先查出哪些算重复:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1; - 检查重复行具体内容:
SELECT * FROM users WHERE email IN ('a@b.com', 'c@d.com') ORDER BY email, id; - 生产环境务必用事务:
BEGIN; DELETE ... ; SELECT ROW_COUNT(); ROLLBACK; -- 确认无误再 COMMIT - 如果表有外键引用,
DELETE可能触发级联或报错,需先查约束:SELECT constraint_name FROM information_schema.key_column_usage WHERE table_name = 'users';
去重后记得重建索引和统计信息
删完数据不等于表就“干净”了。B+树索引里可能残留页碎片,查询计划器的统计信息也可能过期,导致后续查询变慢。
- MySQL:执行
OPTIMIZE TABLE users;(会锁表,慎用)或ALTER TABLE users ENGINE=InnoDB; - PostgreSQL:运行
VACUUM ANALYZE users;,清理死元组并更新统计信息 - SQL Server:用
UPDATE STATISTICS users;和DBCC INDEXDEFRAG(或重建索引) - 别忘了补唯一约束,防止未来再插入重复:
ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);
真正麻烦的不是语法,而是怎么定义“重复”——业务上两个看似相同的邮箱,可能一个是带空格的注册入口,一个是 API 自动填充的;动手前得和产品、运营对齐规则。

















