HAVING必须配合GROUP BY使用,用于过滤分组后的聚合结果;WHERE后直接写HAVING会报错;GROUP BY字段须为SELECT中非聚合列;重复值需用COUNT(*)>1在HAVING中判断。

HAVING必须配合GROUP BY才能生效
直接在WHERE后写HAVING会报错,因为HAVING作用于分组后的结果,不是单行数据。它只能跟在GROUP BY之后,用来过滤分组聚合后的结果集。
常见错误现象:ERROR: syntax error at or near "HAVING",基本都是漏了GROUP BY或把它放在WHERE后面。
-
GROUP BY字段必须是SELECT中出现的非聚合列(或其前缀),否则某些数据库(如MySQL严格模式、PostgreSQL)会报错 - 想查“哪些邮箱重复”,就
GROUP BY email;想查“每个部门里姓名重复的人”,就得GROUP BY dept_id, name - 别在
HAVING里用未聚合的列,比如HAVING created_at > '2023-01-01'是非法的——这个条件该放WHERE里
用COUNT(*) > 1定位重复值
HAVING本身不识别“重复”,得靠聚合函数暴露重复事实。COUNT(*) > 1是最常用、最直观的判断方式,表示该分组至少有两条记录。
示例:查出所有重复的email
SELECT email, COUNT(*) AS cnt FROM users GROUP BY email HAVING COUNT(*) > 1;
- 如果还想看具体是哪些用户,不能只靠这个查询——它只返回分组摘要。需要再关联原表或用窗口函数
- 注意
COUNT(*)和COUNT(email)的区别:后者会忽略NULL值,而重复判定通常要包含NULL是否算“相同”。多数场景用COUNT(*)更稳妥 - 性能上,
GROUP BY + HAVING会触发排序或哈希分组,大数据量时建议确保email字段有索引
结合窗口函数获取完整重复记录(不止分组摘要)
上面的GROUP BY方案只返回重复值和次数,但业务常需要“把所有重复的原始行都捞出来”。这时HAVING自身做不到,得换思路:
用COUNT(*) OVER (PARTITION BY email)给每行打上“所属分组的总行数”,再在外层筛选:
SELECT id, email, name
FROM (
SELECT id, email, name,
COUNT(*) OVER (PARTITION BY email) AS cnt
FROM users
) t
WHERE cnt > 1;
- 这个写法不依赖
GROUP BY,也不需要HAVING,但逻辑等价且返回完整行 - 窗口函数在PostgreSQL、SQL Server、Oracle、MySQL 8.0+都支持;SQLite和旧版MySQL不支持,得退回到自连接或子查询方案
- 如果表很大,
PARTITION BY字段没索引,窗口函数可能比GROUP BY更慢,因为要扫描全表并维护窗口状态
NULL值在重复判断中的特殊行为
SQL标准规定:NULL = NULL为UNKNOWN,所以GROUP BY默认把所有NULL归为同一组——这意味着多个NULL邮箱会被当作“重复”。
如果你的业务认为NULL不算有效值、不应参与重复校验,得显式排除:
SELECT email, COUNT(*) AS cnt FROM users WHERE email IS NOT NULL GROUP BY email HAVING COUNT(*) > 1;
- 别指望
GROUP BY email自动跳过NULL——它不会 - 某些数据库(如PostgreSQL)提供
GROUP BY email NULLS NOT DISTINCT语法,但兼容性差,不建议依赖 - 如果字段允许
NULL且业务语义模糊,最好先确认:多个NULL到底算不算“数据异常”
NULL的分组行为和窗口函数的版本兼容性。尤其当测试数据没NULL、上线后突然冒出一堆“重复NULL”报警,就是这里没兜住。

















