HAVING必须跟在GROUP BY后,用于分组后筛选,可含聚合函数;WHERE用于分组前过滤,不可用聚合函数;二者混用时需注意执行顺序与列可见性,复杂逻辑应使用子查询或CTE。

HAVING 必须跟在 GROUP BY 后面,不能单独用
很多人写 HAVING 时直接接 SELECT,结果报错 SQLSTATE[HY000]: General error: 1140 Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal。这是因为 HAVING 是对分组后的结果做筛选,没分组就用 HAVING,数据库根本不知道“按什么分组后筛选”。
正确顺序只能是:GROUP BY → HAVING → ORDER BY(可选)。
-
HAVING的条件中可以包含聚合函数,比如COUNT(*) > 5、AVG(price) >= 100 - 但
WHERE不能用聚合函数,它在分组前过滤;HAVING才是分组后过滤 - 如果只想要单个分组的聚合值(比如全表平均价),不用
GROUP BY,那也根本不需要HAVING—— 直接用WHERE或子查询
WHERE 和 HAVING 混用时,执行顺序决定性能和结果
写成 SELECT dept, AVG(salary) FROM emp WHERE status = 'active' GROUP BY dept HAVING AVG(salary) > 5000,逻辑上是:先用 WHERE 排掉非活跃员工,再按部门分组,最后筛出平均工资超 5000 的部门。
如果把 status = 'active' 放到 HAVING 里(HAVING status = 'active'),会报错 —— 因为 status 不在 GROUP BY 列里,也不是聚合值,分组后它已不唯一。
-
WHERE过滤行,减少后续分组数据量,通常更高效 -
HAVING过滤组,只能依赖GROUP BY中的列或聚合表达式 - 想查“每个部门里入职满 3 年的员工平均薪资”,得先
WHERE hire_date ,再 <code>GROUP BY dept,最后HAVING(如果需要组级条件)
MySQL 8.0+ 和 PostgreSQL 对 HAVING 的 NULL 处理略有差异
当聚合结果为 NULL(比如某组全为 NULL 值的字段求 AVG),HAVING COUNT(*) > 0 仍会保留该组(因为计数不为零),但 HAVING AVG(x) > 100 在 MySQL 中会让该组被跳过(NULL > 100 判为 false),而 PostgreSQL 默认行为一致,但若开启 transform_null_equals = on 可能影响判断逻辑。
- 稳妥写法是显式处理:
HAVING AVG(price) IS NOT NULL AND AVG(price) > 100 - 避免依赖默认的三值逻辑(true/false/unknown),尤其跨数据库迁移时
- 用
COUNT(col)而非COUNT(*)判断某列非空记录数,语义更清晰
嵌套聚合场景下,HAVING 无法替代子查询
想查“平均订单金额超过全站平均值的用户”,不能写 HAVING AVG(order_amount) > AVG(order_amount) —— 这里两个 AVG 作用域不同,SQL 不允许在同一层引用聚合结果作比较。
必须用子查询或窗口函数:
SELECT user_id, AVG(order_amount) AS avg_per_user FROM orders GROUP BY user_id HAVING AVG(order_amount) > ( SELECT AVG(order_amount) FROM orders );
-
HAVING只能引用当前分组内的聚合结果,不能访问其他分组或全表聚合 - 复杂比较逻辑(如 Top N 分组、组间比率)基本都要靠子查询或 CTE
- 别为了“省一行”硬套
HAVING,该拆就拆,可读性和正确性优先
WHERE,后者才轮到 HAVING;而一旦涉及组和组之间的关系,HAVING 就没用了。

















