最直接方式是用group by+having count()>1统计重复值,where不能用聚合函数;查全部重复记录需窗口函数或自连接;多字段联合重复要注意null、大小写及索引优化。

用 GROUP BY + COUNT() 统计重复值
查重复数据最直接的方式是分组后数次数,COUNT() 和 GROUP BY 必须一起用,单独用 COUNT(*) 只会返回总行数,毫无意义。
常见错误是写成 SELECT name, COUNT(*) FROM users WHERE COUNT(*) > 1 —— 这语法直接报错,WHERE 不能引用聚合函数结果。
- 正确写法:先
GROUP BY字段,再用HAVING筛选分组后的结果 -
HAVING是唯一能过滤聚合结果的子句,WHERE在分组前执行,无法看到COUNT()值 - 如果要查多个字段组合是否重复(比如姓名+手机号),就把它们全写进
GROUP BY
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
查出所有重复行的完整记录(不只是统计)
上面只返回重复值和次数,但业务中往往需要看到具体哪几条记录撞了。这时候得用窗口函数或自关联,不能只靠 GROUP BY。
MySQL 8.0+、PostgreSQL、SQL Server 都支持 ROW_NUMBER(),是最稳妥的做法;老版本 MySQL 只能靠自连接,容易漏或重复。
- 用
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id)给每组重复数据编号 - 再外层筛选
rn > 1就拿到所有“非首条”的重复行 - 注意
ORDER BY要明确(比如按id),否则窗口函数行为不稳定
SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn FROM users ) t WHERE t.rn > 1;
多字段联合去重时的常见陷阱
查「姓名+电话+地址」三者完全一致才算重复?很多人直接 GROUP BY name, phone, address,但空值(NULL)会导致意外结果:两个 NULL 不等于彼此,所以含 NULL 的行永远不算重复。
- 解决办法:用
COALESCE(phone, '')或IFNULL(phone, '')把NULL转成统一占位符再分组 - 字符串比较还要注意大小写和空格,
TRIM(UPPER(name))更可靠 - 如果字段类型混用(比如
phone是数字型但存了带横线的字符串),先统一格式再比
性能问题:大表查重复很慢怎么办
没索引时,GROUP BY 或 PARTITION BY 会触发全表扫描+临时表排序,千万级表可能卡住。关键不是换写法,而是加索引。
- 对用于判断重复的字段建复合索引,比如查
email重复,就建INDEX idx_email ON users(email) - 如果是多字段联合重复,索引顺序要和
GROUP BY字段顺序一致,例如GROUP BY a, b, c→INDEX idx_a_b_c ON t(a,b,c) - 别在
SELECT *场景下盲目加索引,先用EXPLAIN看执行计划,确认瓶颈真在分组阶段
真正麻烦的是既要查重复又要导出原始数据——这时窗口函数的执行代价可能比自连接还高,得实测对比。索引和数据分布比语法选择影响更大。











