WHERE在分组前过滤行,HAVING在分组后过滤组;占比类条件必须用HAVING或窗口函数实现,WHERE无法使用聚合函数。

WHERE 和 HAVING 的分工必须分清
WHERE 在分组前过滤行,HAVING 在分组后过滤组。想筛“某组占比 > 80%”这种条件,WHERE 根本做不到——它看不到 COUNT(*) 或 SUM() 这类聚合结果。
常见错误是把比例计算写在 WHERE 里,比如:WHERE COUNT(*) / (SELECT COUNT(*) FROM t) > 0.8,这会直接报错:「invalid use of group function」。
- 分组占比类逻辑,
HAVING是唯一合法出口 -
WHERE只能用原始字段(如status = 'active'),不能含任何聚合函数 - 如果需要全局总数作分母,得用子查询或窗口函数,不能硬塞进
WHERE
用窗口函数算占比再过滤更灵活
MySQL 8.0+、PostgreSQL、SQL Server 都支持窗口函数,比嵌套子查询更直观,也避免重复扫描表。
例如统计每个 category 占总订单数比例是否超 15%:
SELECT category, cnt, ROUND(100.0 * cnt / total, 2) AS pct
FROM (
SELECT category,
COUNT(*) AS cnt,
SUM(COUNT(*)) OVER() AS total
FROM orders
GROUP BY category
) t
WHERE pct > 15;-
SUM(COUNT(*)) OVER()是关键:在分组后对各组计数再求和,拿到全局总数 - 这里用
WHERE是安全的,因为pct是子查询里的普通列,不是聚合结果 - 旧版本 MySQL(HAVING 组合
旧版 MySQL 只能靠子查询 + HAVING 拼接
MySQL 5.7 或更早版本没窗口函数,又想筛占比,就得把全局总数算出来当常量用。
典型写法是把总数塞进 HAVING 的右边:
SELECT category, COUNT(*) AS cnt FROM orders GROUP BY category HAVING COUNT(*) > (SELECT COUNT(*) FROM orders) * 0.15;
- 注意:这里比较的是「数量」而非「百分比」,避免除零或浮点精度干扰
- 子查询
(SELECT COUNT(*) FROM orders)每次都会执行,大表慎用;可提前存到变量或临时表 - 如果分母不是全表而是某条件下的总数(比如只算 status='paid' 的订单),子查询里必须复现相同
WHERE条件,否则比例失真
GROUP BY 字段漏写导致 HAVING 行为异常
这是线上最隐蔽的坑:MySQL 在 sql_mode 不严格时,允许 SELECT 里出现未出现在 GROUP BY 中的非聚合字段,但 HAVING 对这些字段的引用可能返回任意一行值,毫无意义。
比如误写成:
SELECT category, name, COUNT(*) FROM orders GROUP BY category HAVING COUNT(*) > 100;
-
name没在GROUP BY里,也没用聚合函数包裹,MySQL 可能返回该组第一行、最后一行或随机一行的name,不可靠 - 此时
HAVING虽然能跑通,但结果中name完全不能代表该组特征 - 开启
ONLY_FULL_GROUP_BY模式可强制报错,推荐在开发环境默认启用
真正要查占比,核心永远落在「先分组、再算比例、最后过滤组」这三步上,中间少一步,结果就容易偏。

















