用case when嵌套在聚合函数里配合group by,是实现多条件动态聚合最稳、兼容性最好的方式;where仅能行级过滤,having仅能筛选分组结果,均无法生成多列统计。

直接用 CASE WHEN 套在聚合函数里,比写多个子查询或 UNION 快得多,也更易读。
为什么不能只靠 WHERE + GROUP BY?
因为 WHERE 是在分组前过滤,一旦加了 WHERE status = 'done',其他状态的数据就彻底丢掉了,没法在同一行里对比「待办」「进行中」「已完成」各自的数量。你真正需要的是「对每条记录做条件判断,再汇总」,而不是「先筛数据再分组」。
常见错误现象:
– 写了三个独立 SELECT COUNT(*) FROM t WHERE status = 'x' 拼结果,查 3 次表,IO 和网络开销翻倍
– 用 UNION ALL 合并三行,结果是 3 行,不是 1 行宽表,后续难处理
- 正确思路:让一次扫描完成全部状态计数
- 核心动作:在
COUNT()或SUM()内部用CASE WHEN返回 1 或 0 - 注意:别漏掉
ELSE 0,否则NULL会被COUNT()忽略、SUM()当 0 处理——行为不一致
COUNT(CASE WHEN ...) 和 SUM(CASE WHEN ...) 怎么选?
二者都能实现,但语义和容错性不同:
-
COUNT(CASE WHEN status = 'done' THEN 1 END):只统计「非 NULL」的分支,ELSE缺失时自动为NULL,安全;但无法嵌套表达式(比如COUNT(1)写法无效) -
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END):必须显式写ELSE 0,否则NULL参与求和会让整列变NULL;好处是逻辑更直白,且支持更复杂计算(比如按优先级加权:SUM(CASE WHEN priority='high' THEN 2 WHEN priority='low' THEN 1 ELSE 0 END)) - 性能上无差别,执行计划一样,选哪个纯看可读性和后续扩展需求
实际写法示例(MySQL / PostgreSQL / SQL Server 通用)
SELECT COUNT(*) AS total, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_cnt, SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_cnt, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) AS done_cnt, AVG(CASE WHEN status = 'done' THEN duration_days END) AS avg_done_duration FROM tasks;
说明:
– 最后一行用了 AVG(CASE ...),注意这里没写 ELSE,因为 AVG 本就会忽略 NULL,正好只算「done」记录的 duration_days
– 所有 CASE 共享同一轮全表扫描,没有重复 I/O
– 如果表有索引 (status),优化器可能走索引范围扫描,但 CASE 本身不阻止索引使用
容易被忽略的兼容性细节
某些旧版 SQLite 或特定配置的 Hive 不支持在聚合函数内直接嵌套 CASE WHEN(极少见),此时可退化为子查询,但代价是性能下降:
SELECT (SELECT COUNT(*) FROM tasks WHERE status = 'pending') AS pending_cnt, (SELECT COUNT(*) FROM tasks WHERE status = 'in_progress') AS in_progress_cnt, ...
不过只要数据库支持标准 SQL-92,上面的 SUM(CASE...) 写法就可靠。真正的陷阱不在语法,而在字段值本身——比如 status 有空格、大小写混用('Done' vs 'done')、或含不可见字符,会导致条件不匹配。上线前务必用 SELECT DISTINCT TRIM(UPPER(status)) FROM tasks 探查真实取值。










