GROUP BY 后筛选聚合结果必须用 HAVING,WHERE 仅用于分组前行过滤;HAVING 只能引用 GROUP BY 字段或聚合表达式,且需在单个子句中写全多条件逻辑。

GROUP BY 后面必须用 HAVING,不能用 WHERE
WHERE 是在分组前过滤行,HAVING 才是在分组后对聚合结果做条件筛选。比如想找「买了 3 种以上商品的用户」,WHERE COUNT(*) > 3 会报错,因为 COUNT(*) 在 WHERE 阶段还没计算出来。
常见错误现象:ERROR: column "xxx" must appear in the GROUP BY clause or be used in an aggregate function —— 这通常是因为 SELECT 或 HAVING 里用了非分组字段又没聚合,或误把 HAVING 当 WHERE 用。
-
HAVING只能引用 SELECT 中出现的聚合表达式(如COUNT(product_id))或分组字段(如user_id) - 如果需要同时按用户和年份分组,
GROUP BY user_id, EXTRACT(YEAR FROM order_date),那么HAVING里只能用这两个字段或其聚合值 - WHERE 可以加速:先用
WHERE order_date >= '2024-01-01'缩小数据集,再 GROUP BY + HAVING,性能更好
用 HAVING 筛选「多次购买同一商品」的用户
这种行为识别依赖「商品维度的重复计数」,不能只看用户总订单数。关键在于分组粒度要落到 (user_id, product_id),再对次数聚合。
示例:找出「对同一商品下单 ≥ 2 次的用户」
SELECT user_id, product_id, COUNT(*) AS buy_times FROM orders GROUP BY user_id, product_id HAVING COUNT(*) >= 2;
- 注意:这里
GROUP BY user_id, product_id是核心,漏掉product_id就变成统计用户总单数了 - 若还需关联用户姓名,得用 JOIN,不能直接在 SELECT 加
user_name(除非它也在 GROUP BY 里) - 某些数据库(如 MySQL 5.7 默认)允许 SELECT 非分组字段,但逻辑易错,建议始终严格匹配 GROUP BY 字段
组合多个聚合条件时,HAVING 要写全逻辑表达式
比如找「总消费超 5000 且至少买过 5 件商品的用户」,不能拆成两个 HAVING,也不能用 AND 简单拼接——必须在一个 HAVING 里写清所有条件。
正确写法:
SELECT user_id, SUM(amount) AS total_spent, COUNT(*) AS order_count FROM orders GROUP BY user_id HAVING SUM(amount) > 5000 AND COUNT(*) >= 5;
- 错误写法:
HAVING SUM(amount) > 5000换行再写HAVING COUNT(*) >= 5—— 大部分 SQL 引擎只认最后一个 HAVING - 如果想排除退款订单,务必在 WHERE 阶段过滤
WHERE status != 'refunded',否则 COUNT(*) 和 SUM(amount) 会包含无效记录 - 浮点数比较要小心:
SUM(amount) > 4999.999比> 5000更稳妥(取决于精度设置)
用子查询或窗口函数替代复杂 HAVING 的场景
当需要「每个用户的最高单笔金额 > 平均单笔金额的 2 倍」这类跨层级比较时,HAVING 本身无法引用其他分组的聚合结果,硬写会报错或逻辑错误。
这时该换思路:
- 用子查询先算出全局平均:
SELECT user_id, MAX(amount) FROM orders GROUP BY user_id HAVING MAX(amount) > (SELECT AVG(amount) FROM orders)—— 但注意子查询里没过滤状态,可能不准 - 更稳的是用窗口函数:
SELECT DISTINCT user_id FROM (SELECT user_id, amount, AVG(amount) OVER() AS avg_all FROM orders) t WHERE amount > avg_all * 2 - HAVING 不支持窗口函数,所以这类需求本质已超出 GROUP BY + HAVING 的能力边界,强行套用反而难调试
真正容易被忽略的是:HAVING 的执行时机在 GROUP BY 之后、ORDER BY 之前,但它看不到任何未出现在 GROUP BY 或聚合函数里的列值——哪怕那列在原始表中存在且非空。

















