用group by + having可快速定位重复值,如select email, count() from users group by email having count() > 1;需注意where不能用聚合函数,having才是关键。

用 GROUP BY + HAVING 快速定位重复值
想查出哪些记录重复了,最直接的方式不是嵌套子查询,而是用分组聚合。比如查 users 表中邮箱重复的行:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;注意这里
HAVING 是关键——WHERE 不能用聚合函数,但 HAVING 可以。如果漏写 HAVING 或错写成 WHERE COUNT(*) > 1,会直接报错。用子查询配合 ROW_NUMBER() 删除重复但留一条
真正需要“保留一条、删其余”的场景,靠 GROUP BY 不够,得借助窗口函数。MySQL 8.0+、PostgreSQL、SQL Server 都支持:
DELETE FROM users
WHERE id NOT IN (
SELECT id FROM (
SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
) t
WHERE t.rn = 1
);重点在 PARTITION BY email:按邮箱分组;ORDER BY id 决定哪条被保留(最小 id)。如果表没主键或 id 不唯一,换成有业务意义的字段,比如 created_at。低版本 MySQL(5.7 及以下)怎么绕过窗口函数
没有 ROW_NUMBER() 就得用自连接或相关子查询,性能差但可行。例如查出每组重复中“最小 id”以外的记录:
SELECT u1.* FROM users u1 INNER JOIN users u2 ON u1.email = u2.email AND u1.id > u2.id;这个结果就是该删的行。如果要删,把
SELECT 换成 DELETE u1 即可。注意:必须加 u1.id > u2.id,否则会匹配到自身,导致误删;且大表执行很慢,建议加 INDEX(email)。用 EXISTS 子查询判断某条是否为重复中的“首条”
某些场景不需要删数据,只是标记或过滤——比如只查每个邮箱最早注册的用户。这时用 EXISTS 更清晰:
SELECT * FROM users u1 WHERE NOT EXISTS ( SELECT 1 FROM users u2 WHERE u2.email = u1.email AND u2.id 逻辑是:“不存在另一个同邮箱且 <code>id</code> 更小的记录”,那当前这条就是该邮箱的第一条。这个写法兼容所有 SQL 方言,但要注意 <code>u2.id 不能写反,否则逻辑全错。<p>真正麻烦的不是语法,而是“保留哪一条”的业务定义没对齐:是按时间?按主键?还是某个状态字段?一旦条件模糊,<code>ORDER BY</code> 或比较逻辑就会出偏差,后面所有操作都白做。</p></code>










