having 是筛选分组的唯一合法方式,where 只能过滤原始行且不可用聚合函数;having 仅支持分组列、聚合函数及其组合;排除原始数据用 where;改写分组键需用 case when。

WHERE 不能替代 HAVING 来筛分组
想排除某些分组,比如“只保留订单数超过 5 的城市”,HAVING COUNT(*) > 5 是唯一合法路径。WHERE 在分组前执行,它只能过滤原始行(如 WHERE status = 'completed'),但无法访问分组后的聚合结果。写成 WHERE COUNT(*) > 5 直接报错:ERROR: aggregate functions are not allowed in WHERE。
HAVING 必须用分组字段或聚合函数
HAVING 子句里能写的只有三类东西:分组列(如 city)、聚合函数(如 COUNT(*)、SUM(amount))、它们的组合或常量。常见错误包括:
- 写
HAVING status = 'cancelled'→ 报错,status不在GROUP BY列表里,也不在聚合中 - 写
HAVING amount > 1000→ 报错,amount是单行字段,未聚合也未分组 - 正确写法示例:
HAVING SUM(amount) > 50000或HAVING COUNT(*) BETWEEN 2 AND 10
要排除某类原始数据,必须用 WHERE
如果目标是“不把已取消订单计入任何统计”,这不是筛分组,而是筛行——得用 WHERE 提前干掉它们:
SELECT city, SUM(amount) FROM orders WHERE status != 'cancelled' GROUP BY city- 多值排除:
WHERE status NOT IN ('cancelled', 'failed', 'draft') - 混用场景:
WHERE created_at >= '2026-01-01' GROUP BY user_id HAVING COUNT(*) > 3(先限时间范围,再筛高频用户)
CASE WHEN 改写分组键才是真“忽略标识”
当需求是“让 category = 'temp' 的记录不单独成组,但依然参与总数统计”,WHERE 会丢数据,HAVING 无能为力。必须重写分组逻辑:
- 写成:
GROUP BY CASE WHEN category = 'temp' THEN 'other' ELSE category END -
SELECT、HAVING、ORDER BY中所有引用该字段的地方,都得同步用这个CASE表达式 - 注意:原字段索引失效;PostgreSQL 可建表达式索引,MySQL 基本没法优化
真正容易被忽略的是执行顺序的刚性:WHERE → GROUP BY → HAVING 不可逆。试图在 HAVING 里“回溯”原始字段,本质是逻辑错位。











