HAVING是唯一能在分组后对COUNT(*)等聚合结果过滤的语法;WHERE在分组前执行,无法使用聚合函数,误用会报错;必须将聚合条件置于GROUP BY后的HAVING中,且需注意各数据库对非聚合字段和别名的兼容性差异。

用 HAVING 筛选分组后数量异常的类别
直接说结论:HAVING 是唯一能在分组后对聚合结果(比如 COUNT(*))做条件过滤的语法,WHERE 在分组前就执行,根本看不到分组统计值。
常见错误是写成 WHERE COUNT(*) > 10 —— 这会报错 ERROR: aggregate functions are not allowed in WHERE,因为 WHERE 阶段 COUNT(*) 还没算出来。
- 必须把聚合条件挪到
HAVING子句里,且只能跟在GROUP BY后面 -
HAVING的执行顺序在GROUP BY之后、ORDER BY之前,逻辑上就是“先分组,再筛组” - 如果还带了
WHERE,它只负责过滤原始行(比如排除status = 'deleted'),不影响分组逻辑
查“记录数少于 3 条”的异常分类
业务中常要定位数据稀疏的分类,比如某商品类目下只有 1–2 条商品,可能是录入遗漏或同步失败。
示例:查 product 表中每个 category_id 下商品数
SELECT category_id, COUNT(*) AS cnt FROM product GROUP BY category_id HAVING COUNT(*) <p>注意点:</p>
- 别漏写
GROUP BY—— 没它HAVING会直接报错 -
HAVING中的字段必须是SELECT列表里的聚合列或GROUP BY字段,不能写未分组的普通列(如name) - 如果想同时看具体有哪些商品,得用子查询或
JOIN关联原表,HAVING本身不保留明细
HAVING 和 WHERE 混用时的性能与语义区别
两者位置和作用完全不同,混用不是“多加一层过滤”,而是分工明确:
-
WHERE status != 'draft':先剔除草稿,再分组,影响分组基数 -
HAVING COUNT(*) > 5:对已分好的组,只留下成员数超 5 的组 - 把本该放
WHERE的条件误放HAVING(比如HAVING created_at > '2024-01-01'),会导致全表分组后再过滤,性能暴跌,且可能因created_at不在GROUP BY中而报错
简单记:能用 WHERE 过滤的,绝不塞进 HAVING;HAVING 只留给真正需要“按聚合结果筛选”的场景。
MySQL / PostgreSQL / SQL Server 对 HAVING 的兼容性差异
核心语法一致,但细节上容易踩坑:
- PostgreSQL 要求
HAVING中所有非聚合字段必须出现在GROUP BY中,MySQL(默认 SQL mode)有时允许“宽松模式”,但开启ONLY_FULL_GROUP_BY后行为就和 PG 一致 - SQL Server 不支持在
HAVING中使用列别名(如HAVING cnt > 3会报错),必须写完整表达式HAVING COUNT(*) > 3 - 所有数据库都不允许在
HAVING中引用窗口函数(如ROW_NUMBER()),那属于更上层的计算阶段
跨库迁移时,最常翻车的是依赖 MySQL 宽松模式写的 HAVING,一换 PG 或严格模式 MySQL 就崩。










