having必须跟在group by后,用于分组后过滤组;where在分组前过滤行,不可用聚合函数。二者执行顺序为where→group by→having,having只能引用聚合结果或分组字段,不能替代where的行级筛选。

HAVING 必须跟在 GROUP BY 后面,不能替代 WHERE
WHERE 在分组前过滤行,HAVING 在分组后过滤组——这是最常混淆的点。如果你写了 WHERE COUNT(*) > 1,会直接报错,因为 COUNT(*) 是聚合函数,WHERE 不允许用它。
典型错误现象:ERROR: column "count" does not exist 或 aggregate functions are not allowed in WHERE。
-
WHERE只能用原始列(如status、created_at),不能用SUM()、COUNT()、AVG() -
HAVING只能出现在GROUP BY之后,且必须基于聚合结果或分组列 - 如果不需要分组,只想要全局聚合条件(比如“总订单数 > 100”),就不用
GROUP BY,改用子查询或SELECT ... HAVING(部分数据库如 PostgreSQL 支持无 GROUP BY 的 HAVING,但 MySQL 不支持)
HAVING 中引用字段必须是 SELECT 或 GROUP BY 中出现的
你不能在 HAVING 里写一个没出现在 SELECT 列表或 GROUP BY 子句里的列——不是语法限制,而是逻辑上无法确定该值属于哪个组。
例如:按 user_id 分组,想筛选 “最大金额 > 500”,就得在 SELECT 或 GROUP BY 中包含 user_id;如果漏了,PostgreSQL 会报 column "user_id" must appear in the GROUP BY clause,MySQL(严格模式下)同理。
- 安全写法:先写
GROUP BY user_id,再在SELECT中显式列出user_id和聚合字段,HAVING只引用这些字段 - 别依赖 MySQL 的非标准行为(如
SELECT name, MAX(price) GROUP BY category中name未被分组),它可能返回任意一行的name,HAVING判断时不可靠 - 如果要用别名(如
SELECT COUNT(*) AS cnt),多数数据库(PostgreSQL、SQL Server)允许在HAVING中直接用HAVING cnt > 5;MySQL 8.0+ 也支持,但旧版需重复表达式HAVING COUNT(*) > 5
HAVING 和性能:避免在 HAVING 里做复杂计算
HAVING 是在所有分组完成后再执行的,意味着数据库已经算完了每个组的聚合值,才开始过滤。如果聚合本身代价高(比如 STRING_AGG(... ORDER BY) 或嵌套子查询),再加重量级 HAVING 条件,不会减少中间计算量。
- 优先把能提前过滤的条件放到
WHERE(比如WHERE status = 'paid'),大幅减少参与分组的行数 - 避免在
HAVING中调用 UDF(用户自定义函数)或复杂表达式,尤其当分组数很大时,会逐组执行多次 - 如果
HAVING条件本质是单值判断(如HAVING COUNT(*) = 1),考虑是否可用NOT EXISTS或窗口函数替代,有时更易优化
常见组合:HAVING + COUNT / SUM / CASE WHEN
实际中最常用的是统计类过滤,比如“找出至少下过 3 单的用户”或“平均消费超 200 的品类”。注意 CASE WHEN 在 HAVING 中必须包裹在聚合函数里。
SELECT user_id, COUNT(*) AS order_count FROM orders WHERE paid_at IS NOT NULL GROUP BY user_id HAVING COUNT(*) >= 3;
如果要按条件统计再过滤(比如“退款订单占比 > 10%”):
SELECT product_category,
COUNT(*) FILTER (WHERE status = 'refunded') * 100.0 / COUNT(*) AS refund_rate
FROM orders
GROUP BY product_category
HAVING COUNT(*) FILTER (WHERE status = 'refunded') * 100.0 / COUNT(*) > 10;
- PostgreSQL 用
FILTER最清晰;MySQL 需用SUM(IF(status='refunded',1,0)) - 除法记得乘
100.0避免整数截断(尤其 MySQL) -
HAVING中重复写表达式很冗余,建议用 CTE 或子查询提升可读性,但注意某些数据库(如 SQLite)不支持 CTE 中引用聚合别名
HAVING 的边界其实很窄:它只负责“筛组”,不负责“筛行”,也不负责“排序”或“限制结果数”。很多人试图用它实现去重或 Top-N,结果绕进死胡同——那该用 DISTINCT、窗口函数或 LIMIT。











