WHERE不能使用聚合函数,因其执行在GROUP BY之前,此时COUNT(*)等值尚未生成;HAVING在分组后执行,可安全过滤聚合结果,且必须配合GROUP BY使用。

HAVING COUNT(*) > 1 是唯一正确写法,WHERE COUNT(*) > 1 会直接报错
为什么不能用 WHERE 过滤聚合结果
WHERE 在分组前执行,它看不到 COUNT(*) 这类聚合值;HAVING 才是在 GROUP BY 分组完成后、对每组聚合结果做筛选的阶段。写成 WHERE COUNT(*) > 1 会导致 SQL 语法错误,MySQL 会明确报错 Invalid use of group function。
常见错误现场:
- 把
HAVING误写成WHERE,尤其在从单表查询顺手改成分组时 - 漏写
GROUP BY,只留HAVING—— 没分组就没有“每组”,HAVING 失去作用对象 - SELECT 列表里混入未分组且非聚合字段(如
SELECT id, email FROM users GROUP BY email),MySQL 8.0+ 默认开启ONLY_FULL_GROUP_BY,直接拒绝执行
查重复值本身:标准三步写法
目标是拿到哪些值重复了、各重复几次,不关心原始行细节。这是最轻量、兼容性最好的方式。
- 必须显式写出
GROUP BY字段(比如email或user_id) -
SELECT列表只能包含:GROUP BY字段(如email)、聚合表达式(如COUNT(*))、或常量 -
HAVING COUNT(*) > 1放在最后,不能加括号或写成HAVING COUNT(*) > 1(HTML 实体不识别)
示例(查邮箱重复):
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
查所有重复的原始行(不止摘要)
上面的语句只返回每组一条汇总,但业务常要导出或删除全部重复记录。这时得回原表捞数据,有两种主流方案:
- 子查询 +
IN(兼容 MySQL 5.7 / PostgreSQL / SQL Server):
注意email IS NULL不会被IN匹配,需额外补OR email IS NULL条件 - 窗口函数(MySQL 8.0+ / PostgreSQL 推荐):
用COUNT(*) OVER (PARTITION BY email),一次扫描完成,避免子查询全表扫描风险 - 索引很重要:如果
email列没索引,GROUP BY email和IN子查询都可能变慢,大表务必加索引
子查询写法示例:
SELECT * FROM users WHERE email IN ( SELECT email FROM users GROUP BY email HAVING COUNT(*) > 1 ) OR email IS NULL;
NULL 和大小写这些细节真会咬人
GROUP BY 把所有 NULL 归为同一组,COUNT(*) 也会统计进去——这符合 SQL 标准,但常被当成 bug。如果你不希望 NULL 参与重复判定,得提前过滤:WHERE email IS NOT NULL。
- 想单独看
NULL出现几次?可用GROUP BY email IS NULL, email(MySQL)或COALESCE(email, '<null>')</null> - 大小写敏感问题:默认 collation 下
'Alice'和'alice'算不同值;统一处理用TRIM(LOWER(email)),但记得GROUP BY和SELECT里保持一致 - 首尾空格干扰:
'test@x.com '和'test@x.com'会被当两个值,查前先TRIM(email)
真正容易卡住人的,往往不是语法,而是 NULL 处理是否显式声明、大小写是否统一、索引有没有建——这些点一漏,结果就和预期对不上。

















