where中不能直接使用count()或avg()等聚合函数,必须用having配合group by过滤分组结果;无group by时数据库隐式视为单组;having条件需合并为一个子句;需查原始记录时推荐用窗口函数。

WHERE 里不能直接用 COUNT() 或 AVG()
聚合函数在 WHERE 子句里会报错,比如 WHERE COUNT(*) > 5 直接语法错误。因为 WHERE 执行时还没分组,根本没算出聚合值。
真正该用的是 HAVING —— 它专为过滤分组后的结果而生,必须配合 GROUP BY 使用。
-
HAVING在GROUP BY之后执行,此时COUNT()、AVG()、MAX()都已计算完毕 - 如果没写
GROUP BY,但用了HAVING,数据库会把整张表当作一个组(隐式单组),也能跑通 - 注意:MySQL 5.7+ 默认开启
sql_mode=only_full_group_by,要求SELECT列要么在GROUP BY中,要么是聚合函数,否则报错
多个聚合条件要写在同一个 HAVING 子句里
别想着拆成多个 HAVING —— SQL 不支持。所有条件得合并进一个 HAVING,用 AND 或 OR 连接。
例如查「订单数 ≥ 3 且平均金额 > 100 的客户」:
SELECT customer_id FROM orders GROUP BY customer_id HAVING COUNT(*) >= 3 AND AVG(amount) > 100;
- 不能写两个
HAVING,也不能把其中一个条件挪到WHERE(除非它不依赖聚合) -
AVG(amount)和COUNT(*)是在同一组内独立计算的,不存在先后依赖 - 如果某个聚合字段可能为
NULL(比如amount有空值),AVG()会自动忽略,但COUNT(*)不会;需要统计非空行数时改用COUNT(amount)
想查原始记录而非分组摘要?用窗口函数或子查询
上面的写法只返回满足条件的分组字段(如 customer_id),如果你要查出这些客户的所有订单详情,就得再套一层。
推荐用窗口函数(兼容性好,性能通常更优):
SELECT *
FROM (
SELECT *,
COUNT(*) OVER (PARTITION BY customer_id) AS cnt,
AVG(amount) OVER (PARTITION BY customer_id) AS avg_amt
FROM orders
) t
WHERE cnt >= 3 AND avg_amt > 100;
- 窗口函数不改变行数,每行都能拿到自己所属组的聚合值
- 避免了子查询 +
JOIN的写法,可读性和执行计划通常更干净 - 注意:SQLite 3.25+、PostgreSQL 8.4+、MySQL 8.0+、SQL Server 2005+ 支持;旧版 MySQL 必须用子查询或临时表
GROUP BY 字段选错会导致 HAVING 逻辑错乱
比如误把 order_date 也加进 GROUP BY,那 COUNT(*) 就变成“每天的订单数”,而不是“每个客户的订单数”——HAVING 条件就完全偏离目标。
- 先明确“按什么分组”:是按用户?产品?时间范围?这个粒度决定了聚合值的语义
- 检查
SELECT中非聚合字段是否全在GROUP BY中,否则容易触发only_full_group_by错误 - 如果业务上需要多层聚合(如“每个客户在每个季度的订单数”,再筛选客户),考虑用 CTE 分步处理,比嵌套子查询更清晰
最常被忽略的一点:HAVING 过滤的是分组结果,不是单条记录;窗口函数才是让每条记录携带聚合视角的正确工具。选错路径,后面所有条件都可能白调。











