having必须跟在group by后,用于分组后过滤;where在分组前过滤行,不可用聚合函数。正确顺序为select...from...group by...having...,having条件只能引用group by字段或聚合表达式。

HAVING 必须跟在 GROUP BY 后面,不能替代 WHERE
WHERE 在分组前过滤行,HAVING 在分组后过滤组。如果你把条件写在 WHERE 里却想用聚合函数(比如 COUNT()、AVG()),会直接报错:ERROR: aggregate functions are not allowed in WHERE。这是最常踩的坑——误以为 HAVING 是“高级版 WHERE”。
正确顺序只能是:SELECT ... FROM ... GROUP BY ... HAVING ...。没有 GROUP BY 就不能用 HAVING(某些数据库如 MySQL 允许语法通过,但逻辑无意义,结果不可靠)。
HAVING 条件中只能引用 SELECT 列表里的分组字段或聚合表达式
比如你写了 GROUP BY user_id,又在 SELECT 中写了 COUNT(*) AS cnt,那么 HAVING 可以写 HAVING COUNT(*) > 5 或 HAVING cnt > 5,但不能写 HAVING order_date > '2024-01-01'——因为 order_date 没出现在 GROUP BY 里,也不是聚合结果,数据库不知道该拿哪一行的值来比。
- ✅ 允许:
HAVING AVG(price) >= 100 - ✅ 允许:
HAVING MAX(created_at) > '2024-06-01' - ❌ 禁止:
HAVING status = 'active'(除非status在 GROUP BY 中)
和 WHERE 联合使用时,优先用 WHERE 过滤再分组,更高效
能用 WHERE 提前筛掉大量数据,就别全量分组后再用 HAVING 剔除。比如查“每个用户近30天的订单数超过5单”,应该:
SELECT user_id, COUNT(*) AS cnt FROM orders WHERE created_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY user_id HAVING COUNT(*) > 5;
而不是把时间条件放到 HAVING 里——那样数据库得先对全部历史订单分组,再逐个判断每组里最新时间是否满足,白白浪费资源。
注意:INTERVAL '30 days' 在 PostgreSQL 中有效;MySQL 用 DATE_SUB(NOW(), INTERVAL 30 DAY);SQLite 用 date('now', '-30 days') —— 函数名和语法差异直接影响能否执行。
NULL 值在 HAVING 中的行为容易被忽略
如果分组字段本身含 NULL(比如 user_id IS NULL),它们会被归为同一组。而聚合函数对 NULL 的处理有默认规则:COUNT(*) 会统计行数,COUNT(column) 会跳过 NULL,AVG() 和 SUM() 同样忽略 NULL 值。这意味着:HAVING COUNT(user_id) > 0 不会过滤掉 user_id IS NULL 的组(只要该组有非 NULL 行),但 HAVING COUNT(*) > 0 总是成立。
真正要排除空用户组?得明确写:HAVING COUNT(*) > 0 AND user_id IS NOT NULL,或者更稳妥地在 WHERE 阶段就加 WHERE user_id IS NOT NULL。
聚合计算本身不报错,但结果是否符合业务预期,取决于你有没有意识到 NULL 参与了分组和统计。










