枚举值覆盖率统计本质是判断某值在分组中是否出现,应使用 COUNT(CASE WHEN...) > 0 或 BOOL_OR/MAX,而非 COUNT(*) 或 SUM;枚举动态时宜用 STRING_AGG/GROUP_CONCAT 对比全集;性能优化需建(category, status)联合索引。

用 COUNT 配合 CASE WHEN 统计各枚举值出现次数
覆盖率本质是「某枚举值在分组中是否出现过」,不是频次,所以不能只靠 COUNT(*)。最直接的做法是为每个枚举值写一个条件计数,再除以分组总行数。
比如字段 status 有 'pending'、'done'、'failed' 三种可能,要算每组里 'done' 的覆盖率(即该状态是否至少出现一次):
SELECT category, COUNT(CASE WHEN status = 'done' THEN 1 END) > 0 AS has_done, COUNT(CASE WHEN status = 'pending' THEN 1 END) > 0 AS has_pending, COUNT(*) AS total_rows FROM orders GROUP BY category;
注意:这里用 > 0 转成布尔值,比用 COALESCE(COUNT(...), 0) > 0 更简洁,且 COUNT 本身对 NULL 不计数,天然适配。
- 别用
SUM(CASE ...)算「出现次数」再判断是否 > 0——多算无意义,性能略差 - 如果枚举值很多(如 10+),手写每个
CASE易错,建议先用元数据生成 SQL(例如查information_schema.columns或预定义枚举表) -
BOOL类型在 PostgreSQL/MySQL 8.0+ 可直接输出true/false,旧版 MySQL 会转成1/0,语义一致
用 BOOL_OR(PostgreSQL)或 MAX(通用)简化布尔聚合
PostgreSQL 提供 BOOL_OR 直接判断组内是否存在某条件为真,比 COUNT(...) > 0 更语义清晰:
SELECT category, BOOL_OR(status = 'done') AS has_done, BOOL_OR(status = 'failed') AS has_failed FROM orders GROUP BY category;
其他数据库没 BOOL_OR,但可用 MAX 替代(因布尔表达式在多数引擎中可隐式转为整数):
- MySQL / SQLite:
MAX(status = 'done')返回1或0 - SQL Server:需显式转
MAX(CAST(status = 'done' AS INT)) - 避免用
MIN或AVG——它们无法准确反映「存在性」
当枚举值不固定时,用 STRING_AGG 或 GROUP_CONCAT 检查缺失项
如果业务允许枚举动态扩展(比如新上线 'reviewing' 状态),硬编码 CASE 就容易漏统计。此时更适合先拉出所有实际出现的值,再对比全集:
PostgreSQL 示例(假设全集存在表 valid_status):
SELECT o.category, vs.value AS expected_status, (o.status_list LIKE '%' || vs.value || '%') AS covered FROM ( SELECT category, STRING_AGG(DISTINCT status, ',') AS status_list FROM orders GROUP BY category ) o CROSS JOIN valid_status vs;
MySQL 替换为 GROUP_CONCAT(DISTINCT status) 即可。
- 这种方法适合做「合规检查」,比如审计某类订单是否覆盖了全部法定状态
- 注意
STRING_AGG默认按字典序排序,若需严格匹配,加ORDER BY子句 - 不要依赖逗号分隔字符串做精确匹配(如
'done'和'redone'会误判),生产环境建议用ARRAY(PG)或 JSON 函数(MySQL 5.7+)替代
性能关键:确保 GROUP BY 字段和枚举字段上有联合索引
这类查询通常扫描全表再分组,慢的主因是缺少索引支撑。如果常按 category 分组并查 status 覆盖率,建联合索引能跳过排序和临时表:
CREATE INDEX idx_category_status ON orders (category, status);
这个索引让引擎能直接按 category 分组,并在每个分组内快速定位不同 status 值,尤其对 COUNT(DISTINCT status) 或 STRING_AGG(DISTINCT status) 效果明显。
- 单列索引
(category)或(status)效果有限——优化器仍需回表或文件排序 - 如果表极大且只查少量枚举值,考虑物化覆盖率结果到汇总表,用触发器或定时任务更新
- 注意:索引列顺序不能颠倒,
(status, category)对本场景无效
枚举值覆盖率看着简单,但一碰动态扩展、大数据量或跨库兼容,立刻暴露 SQL 表达力边界。别迷信一个函数搞定一切,先想清楚你要的是「快查存在性」还是「准查缺失项」,再选路。










