WHERE在分组前过滤行,HAVING在分组后过滤组;WHERE不能用聚合函数,HAVING必须跟GROUP BY后且只能用分组列或聚合表达式。

WHERE 和 HAVING 不能混用,顺序错了直接报错
SQL 执行顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。这意味着 WHERE 在分组前过滤行,HAVING 在分组后过滤组。想用聚合结果(比如 COUNT(*) > 5)做条件?只能写在 HAVING 里。WHERE 里写聚合函数会报错,常见错误信息是:ERROR: aggregate functions are not allowed in WHERE。
-
WHERE可以用原始列(如status = 'active'),不能用COUNT()、SUM()等 -
HAVING必须跟在GROUP BY后面;没GROUP BY时也能用HAVING,但此时整张表算作一个组 - 有些数据库(如 MySQL 5.7+ 默认模式)允许
HAVING引用SELECT中的别名,但 PostgreSQL 和 SQL 标准不支持——得重复写表达式,比如HAVING SUM(amount) > 1000,不能写HAVING total > 1000
GROUP BY 后筛选用户订单数大于 3 的城市:HAVING 是唯一选择
假设你有 orders 表,想查「每个城市中下单用户数超过 3 人的城市」。这里“每个城市”要 GROUP BY city,“用户数”是 COUNT(DISTINCT user_id),而“大于 3”这个条件只能放在 HAVING。
SELECT city, COUNT(DISTINCT user_id) AS user_count FROM orders GROUP BY city HAVING COUNT(DISTINCT user_id) > 3;
- 如果把
> 3条件挪到WHERE,语句就变成筛“单个订单的 user_id > 3”,完全不是你要的逻辑 -
HAVING中的字段要么是GROUP BY列(如city),要么是聚合表达式;不能出现未分组也未聚合的列(如order_id) - 性能上,
WHERE越早过滤掉无用行,GROUP BY处理的数据就越少;所以该用WHERE先筛大范围(如WHERE created_at >= '2024-01-01'),再用HAVING做分组后精筛
MySQL 和 PostgreSQL 对 HAVING 的宽松程度不同
MySQL(尤其旧版本或非严格模式)有时允许 HAVING 引用未出现在 GROUP BY 中的非聚合列,比如:SELECT city FROM orders GROUP BY region HAVING city = 'Shanghai'。这在 PostgreSQL 或标准 SQL 中会直接报错:column "city" must appear in the GROUP BY clause or be used in an aggregate function。
- 这种 MySQL 行为是隐式依赖第一个匹配值,结果不可靠——同一
region下多个city时,返回哪个全看引擎怎么选 - 跨数据库迁移或写可维护 SQL 时,务必让
HAVING中所有非聚合列都出现在GROUP BY列表里,或明确用聚合函数包裹(如MAX(city)) - PostgreSQL 9.6+ 支持
GROUP BY的 functional dependency 推断,但前提是列必须有唯一约束,不能默认依赖
用 WHERE 预过滤能显著减少 HAVING 的计算压力
HAVING 是在分组完成之后才执行的,如果数据量大,先分组再筛,代价很高。真正该优化的地方,往往是前置的 WHERE。
- 比如统计「近 30 天内每类商品的销量总和,且只看销量超 100 的类别」,应该先用
WHERE sale_date >= CURRENT_DATE - INTERVAL '30 days'缩小数据集,再GROUP BY category,最后HAVING SUM(quantity) > 100 - 如果漏掉
WHERE时间过滤,可能要对百万级历史订单分组,而实际只需算最近几千条 - 索引对
WHERE有效,但对HAVING无效——别指望给聚合结果建索引去加速HAVING
最常被忽略的是执行顺序带来的逻辑错位:有人写 HAVING 时下意识当成“更高级的 WHERE”,忘了它根本看不到分组前的行级细节。只要记住一点——能写在 WHERE 里的,绝不要拖到 HAVING;而 HAVING 里出现的任何东西,都得是分完组后还能说得清的。










