HAVING 在 GROUP BY 和聚合计算之后执行,用于过滤分组结果;WHERE 在分组前过滤原始行,不可用聚合函数。二者不可互换,HAVING 必须配合 GROUP BY,且标准 SQL 要求其非聚合字段须出现在 GROUP BY 中。

WHERE 和 HAVING 的执行顺序差异
SQL 中 HAVING 不是“后置的 WHERE”,而是真正作用于分组后的结果集。它在 GROUP BY 完成、聚合函数(如 COUNT()、SUM())计算完毕之后才执行;而 WHERE 在分组前就过滤行,不能用聚合结果。
常见错误现象:把 COUNT(*) > 5 写进 WHERE,报错 ERROR: aggregate functions are not allowed in WHERE —— 因为此时还没分组,压根没有“每组的 COUNT”。
-
WHERE过滤原始行,支持列名、表达式,不支持聚合函数 -
HAVING过滤分组,必须配合GROUP BY,可直接使用COUNT()、AVG()等聚合结果 - 没写
GROUP BY却用了HAVING?部分数据库(如 MySQL 5.7+ 严格模式)会报错ERROR: HAVING clause without GROUP BY
过滤“少于 N 条记录的分组”必须用 HAVING
想筛掉用户订单数不足 3 条的客户?不能靠子查询或临时表绕开——HAVING 就是为此设计的。它天然适配“先分组、再统计、最后按统计值筛选”的逻辑链。
使用场景:报表中隐藏低活跃度用户、剔除样本量过小的实验分组、清洗日志中调用频次过低的接口名。
- 正确写法:
GROUP BY user_id HAVING COUNT(*) >= 3 - 错误写法:
WHERE COUNT(*) >= 3(语法非法) - 如果还想要原始明细行,得用窗口函数或 JOIN 子查询,
HAVING本身只输出分组摘要行
MySQL 与 PostgreSQL 对 HAVING 的兼容性细节
标准 SQL 要求 HAVING 中出现的非聚合字段必须也在 GROUP BY 中列出。但 MySQL(尤其旧版本)允许“宽松模式”:比如 SELECT name, COUNT(*) FROM users GROUP BY dept HAVING COUNT(*) > 1 —— name 没在 GROUP BY 里,MySQL 可能返回任意一个 name,PostgreSQL 则直接报错 column "name" must appear in the GROUP BY clause。
- 跨数据库迁移时,别依赖 MySQL 的隐式分组行为
- 开启
sql_mode=ONLY_FULL_GROUP_BY可让 MySQL 行为更接近标准 - PostgreSQL 8.4+ 默认严格,避免用
HAVING隐藏未分组字段的歧义
性能影响:HAVING 无法利用索引加速
HAVING 是对内存/临时结果集的扫描过滤,不像 WHERE 可下推到存储层用索引跳过大量数据。当分组后仍有上百万组,HAVING COUNT(*) > 100 仍需遍历全部分组结果。
容易踩的坑:以为加了 WHERE status = 'active' 就够快,却忽略了 HAVING 部分成为瓶颈——尤其在没建好组合索引(如 (status, user_id))时,GROUP BY 本身就会变慢,HAVING 只是雪上加霜。
- 优先用
WHERE减少输入行数,再GROUP BY,最后HAVING - 对高频分组字段建索引,尤其是
GROUP BY列和WHERE条件列的组合 - 如果只是要“排除小分组”,且业务允许近似,可考虑用
LIMIT+ 应用层过滤,避开大分组扫描










