分组覆盖率指各分组内满足条件的记录数占该组总记录数的比例,需用count(case when...then 1 end)/count()配合group by计算,严禁在where中过滤条件,须用nullif防除零,100.0保浮点精度。

什么是分组覆盖率?先说清楚定义
分组覆盖率不是 SQL 标准函数,而是业务场景中常见的计算逻辑:对每个分组,统计「满足某条件的记录数」占「该分组总记录数」的比例。比如“每个部门中已提交审批的员工占比”,核心是 COUNT(CASE WHEN ... THEN 1 END) / COUNT(*)。
关键点在于:必须用聚合函数配合 GROUP BY,且分子分母必须在同一分组粒度下计算,不能错层或漏 GROUP BY。
写法错误最常出现在 WHERE 和 HAVING 上
很多人会下意识把条件写进 WHERE,结果算的是全局过滤后的占比,而不是“各组内部的占比”。
常见错误写法:
SELECT dept, COUNT(*) / (SELECT COUNT(*) FROM emp) AS coverage FROM emp WHERE status = 'approved' GROUP BY dept;这算的是「已批准员工在全表中的占比」,不是「每个部门里已批准员工占本部门的比例」。
正确做法是把条件放进聚合内部:
- 分子用
COUNT(CASE WHEN status = 'approved' THEN 1 END)或SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) - 分母用
COUNT(*)(注意不是COUNT(status),后者会忽略 NULL) - 绝对不要在外部加
WHERE status = 'approved',否则该部门没批准记录就直接被剔除,覆盖率变成 100% 的假象
NULL 值和除零问题必须显式处理
COUNT(*) 永远 ≥ 1(只要组内有行),但如果你用的是 COUNT(col) 且该列全为 NULL,分母可能为 0,导致除零错误或结果为 NULL。
更稳妥的写法:
SELECT dept,
ROUND(
100.0 * COUNT(CASE WHEN status = 'approved' THEN 1 END) /
NULLIF(COUNT(*), 0),
2
) AS coverage_pct
FROM emp
GROUP BY dept;
-
NULLIF(COUNT(*), 0)把分母为 0 的情况转成 NULL,避免报错 -
100.0 *强制转为浮点,防止整数除法截断(如 SQLite、MySQL 默认行为) - 如果希望空组也显示(如某部门没人),需改用
LEFT JOIN+ 主维表驱动,不能只查事实表
性能敏感时别嵌套子查询算分母
有人会这么写:
SELECT dept,
(SELECT COUNT(*) FROM emp e2 WHERE e2.dept = e1.dept AND status = 'approved') * 100.0 /
(SELECT COUNT(*) FROM emp e2 WHERE e2.dept = e1.dept) AS coverage
FROM (SELECT DISTINCT dept FROM emp) e1;
这在数据量大时是 O(n²),每组都扫全表。
真实项目里,单次扫描 + 聚合永远比相关子查询快。坚持用 GROUP BY + 条件聚合,数据库能一次完成所有分组计算。
实际跑慢,大概率是 dept 列没索引,或者表太大没分区——覆盖率计算本身不重,瓶颈从来不在公式写法,而在数据访问路径。
分组覆盖率看着简单,但错一括号、漏一个 NULLIF、或者误用 WHERE,结果就完全失真。业务方拿这个数字做决策,差个百分之一可能影响资源分配,所以每次写完建议用手工小样例验算一遍。











