必须用 sum(case when ... then 1 else 0 end),因为 count(case when) 遇不匹配返回 null 导致计算错误,而 sum 显式控制每行贡献值、语义确定且兼容主流数据库,else 0 是硬性要求以防 null 渗透。

直接用 SUM(CASE WHEN) 并列写多个,别用 COUNT(CASE WHEN),也别拆成多个子查询或 UNION ALL。
为什么必须用 SUM(CASE WHEN ... THEN 1 ELSE 0 END)
因为你要的是“匹配时加 1,不匹配时加 0”,不是“只统计匹配行”。COUNT(CASE WHEN status = 'done' THEN 1 END) 会把不满足条件的行当作 NULL 忽略掉,结果看似对,但一旦加新状态或做列间计算(比如 done_count - pending_count),就容易因隐式 NULL 导致整列变 NULL。
-
SUM显式控制每行贡献值,语义确定,兼容所有主流数据库(MySQL 5.7+、PostgreSQL、SQL Server) -
ELSE 0是硬性要求——漏掉它,某状态全不匹配时,该列结果就是NULL,不是0 - 别指望外层
COALESCE(SUM(...), 0)补救:它掩盖了逻辑缺陷,且无法解决多列之间对齐问题
怎么写才不会漏状态、不丢数据
横向统计本质是“单行输出多个状态计数”,常见于报表宽表或监控看板。关键不是枚举所有可能值,而是确保每个 SUM(CASE WHEN) 独立、互不干扰。
- 每个状态单独写一个
SUM(CASE WHEN status = 'X' THEN 1 ELSE 0 END),别嵌套、别复用同一个CASE - 如果
status可能为NULL或空字符串,显式处理:CASE WHEN status IS NULL THEN 'unknown' WHEN TRIM(status) = '' THEN 'unknown' ELSE status END,再参与判断 - 字段大小写敏感?统一用
UPPER(status) = 'DONE'或提前清洗,别让'Done'和'done'分成两行 - 不想显示某些非法值(如
'deleted_old')?在WHERE中过滤,或在CASE里归并到'other'
性能和索引怎么配合
这类查询通常走全表扫描,但加对索引能明显提速,尤其当表大、状态少、过滤条件固定时。
- 给
status字段建单列索引即可,多数场景下足够;若还常按时间或项目过滤,考虑复合索引,如(status, created_at) - 避免在
CASE里调函数:UPPER(status)会让索引失效,不如应用层或 ETL 阶段统一转大写 - 数据库优化器能识别多个并列
SUM(CASE)为一次扫描,执行计划里rows不会翻倍——但UNION ALL每个分支都独立执行WHERE,实际扫表次数等于分支数 - 状态种类太多(比如 >10 个)?确认业务是否真需要全量横向展开;否则建议前端动态渲染,SQL 只返回竖表格式
最易被忽略的点:ELSE 0 不是风格问题,是防止 NULL 渗透进后续计算的底线。哪怕只统计两个状态,漏写一个 ELSE 0,整列就可能在某个分组里消失——而这种错误不会报错,只会静默出错。











