直接用 GROUP BY + HAVING COUNT() > 2 可查出重复两次以上的数据,因 COUNT() 是聚合函数,只能在 GROUP BY 分组后计算,WHERE 无法使用聚合函数,必须用 HAVING 过滤分组结果。

直接用 GROUP BY + HAVING COUNT(*) > 2 就能查出重复两次以上的数据,不用子查询、不绕弯——前提是分组字段选对,且明确 NULL 是否参与判重。
为什么必须写 HAVING COUNT(*) > 2 而不是 WHERE COUNT(*) > 2
因为 COUNT(*) 是聚合函数,只能在分组后计算。数据库执行顺序是:WHERE → GROUP BY → HAVING。写 WHERE COUNT(*) > 2 会直接报错:ERROR: aggregate functions are not allowed in WHERE。
-
WHERE只能过滤原始行(比如WHERE status = 'active') -
HAVING才能过滤分组结果(比如HAVING COUNT(*) > 2) - 漏掉
GROUP BY就执行HAVING,也会报错或返回空——HAVING没有分组对象可筛
查“重复两次以上”的完整 SQL 写法
目标是拿到重复值本身和出现次数,语句结构固定:
SELECT column_name, COUNT(*) AS cnt FROM table_name GROUP BY column_name HAVING COUNT(*) > 2;
- 如果要查多字段组合重复(如
user_id和order_date同时相同才算),GROUP BY必须写全:GROUP BY user_id, order_date - 想排除
NULL干扰(比如邮箱为空不算重复),加WHERE email IS NOT NULL在GROUP BY前 - 若业务上认为
NULL是有效值且需单独统计,可用GROUP BY email IS NULL, email(MySQL 支持)或COALESCE(email, '<null>')
怎么拿到所有重复的原始行(不止是摘要)
GROUP BY + HAVING 只返回每组一条摘要,但实际常需导出/删除全部重复记录。两种主流做法:
- MySQL 8.0+ / PostgreSQL 推荐窗口函数:
SELECT * FROM ( SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt FROM users ) t WHERE t.cnt > 2;
- 兼容旧版(如 MySQL 5.7)用子查询:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 2 );
注意:若email允许为NULL,IN会跳过它们,得额外补OR email IS NULL
容易被忽略的三个坑
真正上线跑的时候,这些点最容易卡住:
-
SELECT *和GROUP BY混用:MySQL 5.7+ 默认开启ONLY_FULL_GROUP_BY,SELECT *会报错,必须只选分组字段或聚合字段 - 大小写/空格干扰:比如
'Alice '和'alice'在默认 collation 下可能不算重复,要用LOWER(TRIM(email))分组,但索引会失效 - 没建索引却查大表:对千万级表执行
GROUP BY email,若email无索引,全表扫描 + 排序极慢;建索引前先EXPLAIN看是否走索引
最麻烦的从来不是写对语法,而是确认业务里“什么叫重复”——NULL 算不算?大小写敏感吗?空格要不要 trim?这些判断一旦错,查出来的结果就是错的,而且很难一眼发现。

















