最常用、最可靠的查重复方法是用 GROUP BY 配合 HAVING COUNT() > 1,但必须确保 GROUP BY 字段组合准确反映业务意义上的重复定义;COUNT() 必须用于计数,不可用 COUNT(字段) 替代,否则会忽略 NULL 导致漏判。

直接说结论:用 GROUP BY 配合 HAVING COUNT(*) > 1 是最常用、最可靠的查重复方法,但必须注意字段组合的语义是否真代表“业务意义上的重复”。
为什么 HAVING COUNT(*) > 1 能定位重复组
分组后每组对应一个唯一键(由 GROUP BY 字段决定),COUNT(*) 统计的是该键下有多少行。只要大于 1,就说明这些行在指定字段上完全一致——也就是你定义的“重复”。
常见错误是只对单个字段分组(比如只按 email),却忽略业务中真正判定重复的应是多字段组合(如 name + phone + id_card)。
- 必须把所有参与判重的字段都写进
GROUP BY,不能只选其中一部分 -
HAVING是对分组结果过滤,不能用WHERE替代(WHERE在分组前执行,无法访问COUNT(*)) - 如果表很大,记得给
GROUP BY字段建联合索引,否则GROUP BY可能全表扫描
查出重复数据本身,不只是分组摘要
GROUP BY + HAVING 只返回分组摘要(如 email 和出现次数),不返回原始重复行。要看到具体哪几条记录重复,得用子查询或窗口函数。
推荐用 IN 子查询关联原表:
SELECT * FROM users WHERE (email, name) IN ( SELECT email, name FROM users GROUP BY email, name HAVING COUNT(*) > 1 );
注意:(email, name) 是行构造器语法,MySQL 8.0+、PostgreSQL 支持;SQLite 和旧版 MySQL 需改写为两个独立子查询或用 JOIN。
- Oracle 或 SQL Server 用户可改用
ROW_NUMBER() OVER (PARTITION BY ...)窗口函数,性能通常更好 - 如果字段允许 NULL,
(a, b) IN (SELECT a, b ...)可能漏掉含 NULL 的重复组(因 NULL = NULL 不成立),此时需额外处理 NULL 条件
COUNT(*) vs COUNT(字段名) 的区别
查重复必须用 COUNT(*),不是 COUNT(某个字段)。后者会自动忽略该字段为 NULL 的行,导致计数偏小,可能把本该算作重复的组漏掉。
例如:
SELECT user_id, COUNT(*) -- ✅ 包含所有行,NULL 也计 1 FROM logs GROUP BY user_id HAVING COUNT(*) > 1;
SELECT user_id, COUNT(ip) -- ❌ 如果 ip 是 NULL,这行不参与计数 FROM logs GROUP BY user_id HAVING COUNT(ip) > 1;
- 只要字段可能为空,就别用
COUNT(字段)做重复判断 - 想确认某字段是否全非空,可用
COUNT(*) = COUNT(字段)辅助校验
真正难的不是写出这条语句,而是厘清“什么才算重复”——是身份证号相同?手机号+姓名组合相同?还是业务主键以外的自然键冲突?这个定义错了,后面所有 SQL 都白跑。

















