应使用SUM(CASE WHEN...THEN 1 ELSE 0 END)而非COUNT(CASE WHEN...THEN 1 END),因后者遇全不匹配或NULL状态时返回NULL,导致计算错乱;ELSE 0是硬性要求,确保每行贡献确定值,语义清晰且跨数据库一致。

为什么不能用 COUNT(CASE WHEN ... THEN 1 END)
因为 COUNT() 会跳过 NULL,而 CASE WHEN status = 'done' THEN 1 END 在不匹配时默认返回 NULL。结果看似能出数,但一旦某状态在当前分组中完全没出现,该列就变成 NULL 而非 0——后续做减法(如 done_count - pending_count)或导出到前端,整列可能变 NULL,引发计算错乱或崩溃。
更隐蔽的问题是:如果原始数据里 status 本身含 NULL 或空字符串,又没在 CASE 中显式覆盖,这部分行会被静默丢弃,统计值偏小却难以察觉。
SUM(CASE WHEN ... THEN 1 ELSE 0 END) 是唯一稳解
每个状态必须独立写一个 SUM(CASE WHEN ... THEN 1 ELSE 0 END),ELSE 0 不是可选项,是硬性要求:
-
SUM()对NULL敏感:全不匹配时,漏掉ELSE 0→ 整列结果为NULL -
ELSE 0保证每行都贡献确定值(1 或 0),语义清晰,跨数据库一致(MySQL 5.7+、PostgreSQL、SQL Server 全支持) - 多个并列
SUM(CASE...)会被优化器识别为单次扫描,不会重复读表;而用UNION ALL或多次WHERE查询,实际扫描次数翻倍
示例(统计订单三类状态):
SELECT SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN status = 'shipped' THEN 1 ELSE 0 END) AS shipped_count, SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count FROM orders;
状态字段有 NULL 或非法值怎么办
别指望外层加 COALESCE(SUM(...), 0) 补救——它掩盖了逻辑缺陷,且无法解决多列对齐问题。正确做法是在 CASE 内部提前归一化:
- 处理
NULL:CASE WHEN status IS NULL THEN 'unknown' ELSE status END - 处理空字符串:
CASE WHEN TRIM(status) = '' THEN 'unknown' WHEN status IS NULL THEN 'unknown' ELSE status END - 合并非法值(如
'deleted_old'):CASE WHEN status IN ('done', 'completed') THEN 'done' WHEN status = 'deleted_old' THEN 'other' ELSE status END
注意:避免在 CASE 里调 UPPER(status) 这类函数——索引会失效;大小写问题应在 ETL 阶段清洗,或建函数索引(MySQL 8.0+ 支持)。
GROUP BY 场景下容易漏掉的 NULL 分组
当按 user_id 分组统计各用户订单状态数时,若部分订单 user_id IS NULL,GROUP BY user_id 默认把 NULL 当作独立分组,但很多人根本没意识到这组存在,导致统计缺漏。
确认是否要包含该分组:
- 想纳入统计:显式写
GROUP BY COALESCE(user_id, -1)或GROUP BY CASE WHEN user_id IS NULL THEN 'unknown' ELSE CAST(user_id AS CHAR) END - 想排除:在
WHERE加user_id IS NOT NULL,比在CASE里兜底更高效
检查脏数据比例的快捷方式:AVG(CASE WHEN user_id IS NULL THEN 1.0 ELSE 0.0 END) 返回小数,一眼看出 NULL 占比。

















