最可靠写法是GROUP BY字段组合后用HAVING COUNT(*) > 1筛选重复组;SELECT列须全在GROUP BY中或被聚合函数包裹;查完整重复行需JOIN或IN子查询回原表。

怎么用 GROUP BY + HAVING 找出重复行
直接上最常用也最可靠的写法:GROUP BY 字段组合,再用 HAVING COUNT(*) > 1 筛出重复组。关键不是“有没有重复”,而是“哪些字段组合算重复”——比如你查订单表里同一用户多次下单同一商品,就得 GROUP BY user_id, product_id,而不是只按 user_id 分组。
常见错误是漏掉 HAVING,写成 WHERE COUNT(*) > 1,这会报错,因为 COUNT() 是聚合函数,不能在 WHERE 里用。
-
SELECT列必须全在GROUP BY中,或被聚合函数包裹(如MIN(id)) - 想看完整重复行?别只查分组结果,后面要加
JOIN或子查询回原表 - 字符串字段有空格或大小写差异?先用
TRIM()、LOWER()统一再分组,否则'Alice '和'alice'会被当成两组
为什么 COUNT(*) > 1 不等于 COUNT(id) > 1
COUNT(*) 统计每组所有行数,包括 NULL 值;COUNT(id) 只统计 id 非 NULL 的行数。如果某组里有 3 行,其中 2 行 id 是 NULL,那 COUNT(*) 是 3,COUNT(id) 是 1——用后者会漏掉这组重复。
除非你明确想排除 NULL 参与计数,否则一律用 COUNT(*)。
- PostgreSQL 和 MySQL 8.0+ 支持
COUNT(*)优化,性能几乎无差别 - SQL Server 对
COUNT(*)有索引统计优化,比COUNT(列名)更快 - 别用
COUNT(1)图省事——它和COUNT(*)行为一致,但语义不清,容易让同事多想一秒钟
查出重复数据后,怎么拿到全部原始记录
GROUP BY + HAVING 只返回分组摘要,比如 “邮箱 xxx@example.com 出现了 4 次”。真要定位到那 4 条记录,得把分组结果当条件反查原表。
推荐用 IN 子查询,简洁安全:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 );
- 注意:如果
email允许NULL,IN (subquery)会跳过NULL行,得额外补一句OR email IS NULL - 大表慎用——子查询可能触发全表扫描,加
email索引能明显提速 - 想标出每条记录属于第几次重复?用窗口函数
COUNT(*) OVER (PARTITION BY email),但 MySQL 5.7 不支持,得升版本或换写法
GROUP BY 字段顺序会影响结果吗
不影响重复判定逻辑,但影响 GROUP BY a, b 和 GROUP BY b, a 的分组粒度是否一致——只要字段集合相同,结果就一样。真正容易踩坑的是字段类型隐式转换。
比如 GROUP BY phone,而 phone 是 VARCHAR 类型,存了 '00123' 和 '123',数据库不会自动去前导零,它们就是两组。
- MySQL 默认不区分大小写,
'ABC'和'abc'会被归为一组;PostgreSQL 区分,得显式用LOWER(phone) - 日期字段带时分秒?
GROUP BY DATE(created_at)才能按天聚合,否则同一天不同时间也算不同组 - 别在
GROUP BY里用表达式却不加别名——某些旧版 SQLite 会报no such column
重复数据问题的核心永远是“你定义的‘重复’到底指什么”,不是语法有多难。字段含义、空值处理、大小写、前后空格、时区、精度……这些细节没对齐,GROUP BY 写得再标准也没用。










