having必须跟在group by后,用于分组后筛选,条件只能含group by列或聚合函数;where用于分组前过滤行,不可用聚合函数;二者执行顺序不同,混用时需严格遵循where→group by→having逻辑。

HAVING 必须跟在 GROUP BY 后面,不能单独用
很多人写 HAVING 时直接接 SELECT,结果报错 ERROR: syntax error at or near "HAVING"。根本原因是 HAVING 不是过滤行的工具,它只对分组后的聚合结果生效,所以必须先有 GROUP BY —— 没分组就谈不上“分组后筛选”。
常见错误写法:
SELECT user_id, COUNT(*) FROM orders HAVING COUNT(*) > 5;(缺
GROUP BY user_id)正确结构顺序只能是:SELECT ... FROM ... GROUP BY ... HAVING ...
-
HAVING的条件中,所有字段要么是GROUP BY列,要么是聚合函数(如COUNT()、SUM()、AVG()) - 不能用
WHERE里允许的非聚合字段,比如HAVING order_date > '2024-01-01'会报错(除非order_date在GROUP BY中) - 如果只想筛单行数据,用
WHERE;想筛“某个用户下了超过 5 单”,才轮到HAVING
WHERE 和 HAVING 的执行时机完全不同
WHERE 在分组前过滤原始行,HAVING 在分组后过滤聚合行。这个顺序差一点,结果可能天差地别。
比如查“每个城市里订单金额总和超 1000 的客户”:
-
WHERE amount > 100是先剔除所有小于等于 100 的订单,再按客户分组求和 -
HAVING SUM(amount) > 1000是把所有订单都参与分组汇总,再筛出总和达标的人 - 两者逻辑不同,不能互换。漏掉
WHERE可能让低价值订单拉低统计口径;滥用HAVING做行级过滤会导致性能下降(因为得先算完所有组)
HAVING 支持复杂表达式,但注意 NULL 和类型匹配
HAVING 里可以写 COUNT(*) > 0、AVG(price) BETWEEN 10 AND 100、MAX(created_at) > NOW() - INTERVAL '7 days',但有两个实际坑点:
- 聚合结果为
NULL时,比较一律返回FALSE(不是报错),比如HAVING SUM(discount) > 100会跳过所有SUM(discount)为NULL的组 —— 如果你希望包含NULL组,得显式写HAVING SUM(discount) > 100 OR SUM(discount) IS NULL - 不同数据库对
HAVING中列名解析宽松度不同:PostgreSQL 要求严格匹配GROUP BY或聚合字段;MySQL 5.7+ 兼容模式下允许引用SELECT列别名(如HAVING total > 1000),但标准 SQL 不保证支持,跨库迁移时容易翻车
替代方案:用子查询或 CTE 避免 HAVING 语义混淆
当逻辑变复杂(比如要同时筛多个聚合指标、或嵌套条件),硬塞进 HAVING 容易读不懂,也难调试。更清晰的做法是把分组聚合结果拎出来,再外层筛选:
SELECT * FROM ( SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total FROM orders GROUP BY user_id ) t WHERE cnt > 5 AND total > 2000;
这样写的好处:
- 每层职责单一:内层只管分组聚合,外层只管筛选逻辑
- 可复用中间结果(比如后续还要加
ORDER BY total DESC LIMIT 10) - 避免
HAVING里重复写聚合函数(如HAVING COUNT(*) > 5 AND SUM(amount) > 2000),减少计算开销(某些引擎不会自动优化重复聚合)
真正容易被忽略的是:HAVING 的“过滤对象”不是数据行,而是分组桶。理解这点,才能不把它当成 WHERE 的替身用。










