用GROUP BY和HAVING查重复需先按目标字段分组,再用HAVING COUNT(*)>1筛选出现多次的组;若要返回所有重复行,须用子查询或窗口函数配合IN/JOIN。

怎么用 GROUP BY 和 HAVING 找出重复的行
直接看核心逻辑:先按想判断是否重复的字段分组,再用 HAVING COUNT(*) > 1 筛出组内有多条记录的组。这不是“查重”,而是“查哪些值组合出现了多次”。
常见错误是只写 SELECT * 后跟 GROUP BY——这在严格 SQL 模式下会报错,因为非分组字段不能直接选;更关键的是,你真正需要的往往不是“某组里随便一条”,而是“把重复的完整记录都捞出来”。
- 想查「用户名重复」就
GROUP BY user_name - 想查「邮箱+手机号同时重复」就
GROUP BY email, phone -
HAVING COUNT(*) > 1是硬门槛,>= 2也行,但语义不如> 1直观 - MySQL 5.7+ 默认开启
sql_mode=ONLY_FULL_GROUP_BY,所以SELECT *+GROUP BY id这类写法会失败
查出所有重复记录(不止一行代表)
上面的 GROUP BY + HAVING 只返回每组一条聚合结果,比如用户名和出现次数。但业务中常要导出所有重复的原始数据——这时得套一层子查询或用窗口函数。
兼容性优先的做法(适用 MySQL 5.6/PostgreSQL/SQL Server):
SELECT * FROM users WHERE (email, phone) IN ( SELECT email, phone FROM users GROUP BY email, phone HAVING COUNT(*) > 1 );
注意:(email, phone) 是行构造器语法,在 MySQL 和 PostgreSQL 中有效,SQL Server 不支持,得改用 JOIN 或 EXISTS。
- 子查询里别漏
GROUP BY字段,否则HAVING无意义 - 如果字段可能为
NULL,IN会失效(NULL = NULL不成立),得改用IS NOT DISTINCT FROM(PostgreSQL)或显式判空 - 大数据量时,给
GROUP BY字段建联合索引能明显提速
COUNT(*) 和 COUNT(字段) 在去重场景的区别
查重复时几乎总该用 COUNT(*),而不是 COUNT(某个字段)。因为后者会自动忽略该字段为 NULL 的行,导致计数偏小,误判“不重复”。
例如用户表里 phone 允许为空,用 COUNT(phone) > 1 分组,那么所有 phone IS NULL 的记录会被当成一组且计数为 0,根本进不了 HAVING 条件。
-
COUNT(*)统计行数,包含NULL -
COUNT(col)只统计col IS NOT NULL的行 - 除非你明确想排除空值再算重复(极少见),否则一律用
COUNT(*)
为什么有时候 HAVING 报错说“Unknown column”
典型错误:在 HAVING 里引用了 SELECT 列别名,比如 SELECT name AS n FROM t GROUP BY name HAVING n = 'a'。这是语法错误——HAVING 执行时机早于 SELECT 别名绑定,它看不到 n。
正确做法是重复表达式,或用子查询封装:
SELECT name FROM t GROUP BY name HAVING name = 'a';
- 别名不能用于
WHERE、GROUP BY、HAVING,只在ORDER BY和SELECT后续中可用 - 如果表达式复杂(比如
UPPER(TRIM(name))),建议先用子查询计算好再分组,避免重复写 - 某些旧版 SQLite 允许别名进
HAVING,但这不是标准行为,别依赖
实际查重时最容易被绕晕的,是分不清“按什么分组”和“要返回哪些字段”。一个字段重复,不代表整行重复;而你要导出的,往往是整行数据——这时候光靠 GROUP BY + HAVING 是不够的,必须配合外层筛选。










