最直接定位重复值用group by+having count()>1,如select email,count() from users group by email having count(*)>1;需查完整重复行则用in子查询或窗口函数row_number()。

用 GROUP BY + HAVING 找出重复值最直接
想快速定位某列(比如 email)有哪些值出现了多次,GROUP BY 配合 HAVING COUNT(*) > 1 是最常用也最可靠的方式。它不依赖窗口函数,兼容 MySQL 5.7、PostgreSQL、SQL Server 等主流版本。
常见错误是只写 GROUP BY email 却忘了加 HAVING,结果返回的是每组一条聚合行,而非原始重复记录本身。
- 要查出重复的
email值及其出现次数:SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
- 如果需要看到所有重复的完整行(比如带
id和name),得用子查询或IN:SELECT * FROM users WHERE email IN (SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1);
- 注意:如果表很大,
IN子查询可能变慢;MySQL 8.0+ 或 PostgreSQL 可改用JOIN或窗口函数优化
用窗口函数 ROW_NUMBER() 精确标记重复行
当你要区分“第一次出现”和“后续重复项”,或者需要保留原始顺序、做去重前预览,ROW_NUMBER() 是更灵活的选择。但它要求数据库支持窗口函数(MySQL ≥ 8.0,PostgreSQL ≥ 8.4,SQL Server ≥ 2005)。
容易踩的坑是误用 RANK() 或 DENSE_RANK()——它们对相同值分配相同序号,无法单独标识“第2次、第3次出现”的具体行。
- 给每个
email分组内按id排序编号:SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users;
- 再过滤出重复行(
rn > 1即非首次):SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users) t WHERE t.rn > 1;
- 性能提示:
PARTITION BY email会触发排序,若email列无索引,大表下可能显著拖慢查询
WHERE + EXISTS 检查是否存在另一条相同记录
这个写法语义清晰:“找那些存在至少一条其他行、且 email 相同的记录”。适合逻辑复杂、需关联多条件判断的场景,但通常比 GROUP BY 版本慢。
典型错误是忽略自连接的主键排除,导致每行都和自己匹配上,结果全表返回。
- 正确写法必须排除自身:
SELECT u1.* FROM users u1 WHERE EXISTS (SELECT 1 FROM users u2 WHERE u2.email = u1.email AND u2.id != u1.id);
- 如果表有复合唯一约束(如
(email, tenant_id)),这里可以自然扩展为u2.email = u1.email AND u2.tenant_id = u1.tenant_id AND u2.id != u1.id - 没有索引时,
EXISTS的嵌套扫描代价高;务必确保email(或组合字段)上有索引
去重前先确认 NULL 是否参与重复判定
NULL 在 SQL 中不等于任何值,包括另一个 NULL。所以默认情况下,GROUP BY email 会把所有 email IS NULL 的行归为一组——它们彼此“相等”,算作一次重复;而 WHERE email = ... 类条件则完全跳过 NULL。
这点极易被忽略,尤其当业务允许邮箱为空时,你可能以为没重复,其实一堆 NULL 被悄悄聚在一起了。
- 想把 NULL 当普通值处理(即多个 NULL 视为重复):保持原查询即可,
GROUP BY默认如此 - 想排除 NULL 再查重复:
SELECT email, COUNT(*) FROM users WHERE email IS NOT NULL GROUP BY email HAVING COUNT(*) > 1;
- 想单独统计 NULL 的数量:
SELECT 'NULL' AS email, COUNT(*) FROM users WHERE email IS NULL HAVING COUNT(*) > 1;
实际查数据前,先 SELECT COUNT(*), COUNT(email), COUNT(DISTINCT email) FROM users 对比一下,能立刻暴露 NULL 是否在搅局。











