having是专用于分组后过滤的子句,必须配合group by使用,可直接引用聚合函数(如count(*)>3)筛选分组,而where不可用聚合函数;其执行在group by之后,不走索引,仅输出分组摘要。

HAVING 是唯一合法且直接的解法
想筛掉订单数不足 3 条的用户、日志调用少于 5 次的接口、或商品数低于 2 的类目,HAVING 就是为此设计的。它不是“后置 WHERE”,而是在 GROUP BY 和聚合计算完成后,专门用来过滤分组结果的机制。任何试图把 COUNT(*) > 5 写进 WHERE 的写法都会报错:ERROR: aggregate functions are not allowed in WHERE。
必须配合 GROUP BY,且不能省略
-
HAVING必须跟在GROUP BY后面,否则多数数据库(PostgreSQL、MySQL 开启ONLY_FULL_GROUP_BY时)会直接报错:HAVING clause without GROUP BY - 错误写法:
SELECT user_id, COUNT(*) FROM orders HAVING COUNT(*) >= 3→ 缺GROUP BY,语法非法 - 正确写法:
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) >= 3 - 如果还想要原始明细(比如每条订单的
order_id、amount),HAVING本身做不到——它只输出分组摘要行,得改用窗口函数或子查询
别指望 HAVING 加速,它不走索引
HAVING 是对内存中已生成的分组结果集做线性扫描,无法下推到存储层利用索引跳过数据。当分组后仍有几十万组,HAVING COUNT(*) > 100 仍要遍历全部分组结果。如果性能敏感,优先在 WHERE 阶段预过滤无效行(比如先剔除 status = 'deleted' 或 create_time ),减少进入分组的数据量。
跨数据库兼容性陷阱:别依赖 MySQL 的宽松模式
- MySQL 旧版本允许:
SELECT name, COUNT(*) FROM users GROUP BY dept HAVING COUNT(*) > 1——name没出现在GROUP BY,MySQL 可能返回任意一个name - PostgreSQL 和标准 SQL 要求:
name必须显式出现在GROUP BY列表里,否则报错:column "name" must appear in the GROUP BY clause - 迁移或共用 SQL 时,务必开启
sql_mode=ONLY_FULL_GROUP_BY,让 MySQL 行为对齐标准
真正容易被忽略的点是:HAVING 只回答“哪些组数量异常”,但从不告诉你“为什么异常”。要查清是数据缺失、状态全为 NULL、还是时间戳全是 1970-01-01,得靠窗口函数补上下文——HAVING 是起点,不是终点。











