直接用GROUP BY+HAVING COUNT()>1找出重复值,因WHERE不能用聚合函数,必须用HAVING筛选分组后行数大于1的组;查单列重复写GROUP BY email HAVING COUNT()>1,查多列组合则GROUP BY name,phone HAVING COUNT()>1;要获取所有重复原始行需用窗口函数COUNT() OVER(PARTITION BY email)或子查询。

GROUP BY 配合 HAVING 找出重复值
直接用 GROUP BY 本身不会“找出重复数据”,它只是分组;真正筛选出重复项靠的是 HAVING 子句。核心逻辑是:按某列(或某几列)分组后,统计每组行数,只保留行数大于 1 的组。
常见错误是只写 GROUP BY 不加 HAVING,结果返回的是每组一条记录,看不出是否重复;或者误用 WHERE 过滤聚合前的数据,根本筛不到重复组。
- 想查单列重复(比如
email字段重复):SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
- 查多列组合重复(如姓名+手机号同时相同才算重复):
SELECT name, phone, COUNT(*) FROM contacts GROUP BY name, phone HAVING COUNT(*) > 1;
-
COUNT(*)比COUNT(字段名)更稳妥——后者会忽略该字段为NULL的行,可能导致漏判重复组
要显示所有重复行(不只是分组摘要)怎么办?
GROUP BY + HAVING 只返回每组的聚合结果(如邮箱和次数),但业务中常需要看到所有重复的原始记录。这时不能只靠 GROUP BY,得用窗口函数或子查询。
推荐用 COUNT(*) OVER (PARTITION BY ...):它不改变行数,只为每一行算出其所属分组的总行数,再在外层过滤。
- MySQL 8.0+、PostgreSQL、SQL Server 都支持:
SELECT id, email, name FROM ( SELECT id, email, name, COUNT(*) OVER (PARTITION BY email) AS dup_cnt FROM users ) t WHERE dup_cnt > 1; - 旧版 MySQL(
- 注意别在
PARTITION BY里混用可能为NULL的字段——多数数据库中NULL = NULL为 false,会导致本该同组的NULL值被拆成多组
GROUP BY 的字段必须出现在 SELECT 列表里吗?
取决于 SQL 模式。严格模式(如 MySQL 的 sql_mode=ONLY_FULL_GROUP_BY)要求:所有非聚合字段必须出现在 GROUP BY 中;否则报错 Expression #1 of SELECT list is not in GROUP BY clause。
- 关闭该模式能绕过报错,但结果不可靠——数据库会随机选某一行的值填充非分组字段,看似“能跑”,实则数据错乱
- 正确做法是:要么把字段加进
GROUP BY,要么用聚合函数包裹(如MAX(name)),明确语义 - PostgreSQL 默认就严格,不允许 SELECT 中出现未分组也未聚合的字段
性能和索引怎么配合?
GROUP BY 在大数据量下容易慢,尤其没索引时要全表排序+分组。重复检测类查询往往要高频执行,索引设计直接影响能否落地。
- 对用于
GROUP BY的字段(如email)建 B-tree 索引,能显著加速分组过程 - 复合重复判断(如
GROUP BY a, b)时,建联合索引(a, b),顺序不能颠倒——(b, a)对GROUP BY a, b几乎无效 - 如果表有主键且你只需要重复的主键值,可以在子查询中只
SELECT id,减少内存占用;避免在GROUP BY查询里SELECT *
NULL 值的处理方式和 SQL 模式的隐性影响——同一句 SQL 在不同环境可能返回不同结果,甚至报错。动手前先 SELECT @@sql_mode 或查文档确认行为边界。

















