用 CASE WHEN + SUM 统计多状态最高效,需注意漏写 ELSE 0、空值/空格处理、单引号转义;GROUP BY 仅得纵向结果,无法替代横向汇总;WHERE 应先过滤再统计;NULL 状态须用 IS NULL 显式判断。

用 CASE WHEN + SUM 统计多个状态
直接在 SELECT 里对每个状态写一个 SUM(CASE WHEN status = 'xxx' THEN 1 ELSE 0 END),比多次 COUNT 或子查询快,也避免重复扫描表。
常见错误是漏写 ELSE 0——遇到不匹配的行,CASE 返回 NULL,SUM 会跳过它,导致总数偏小。
-
status字段为空或有空格时,= 'paid'不会命中,建议加TRIM(status) = 'paid'或提前清洗 - 状态值含单引号(如
O'Reilly)要转义,写成status = 'O''Reilly' - MySQL 8.0+ 和 PostgreSQL 支持
COUNT(*) FILTER (WHERE status = 'paid'),更简洁,但老版本不兼容
GROUP BY 不能替代多状态统计
如果只写 GROUP BY status,结果是一行一行的状态分布,不是“一行汇总所有状态数量”。需要横向展开,不是纵向分组。
比如想看订单表里 pending、shipped、cancelled 各有多少条,用 GROUP BY 得到三行;而用 CASE WHEN 可以得到一行三列,方便后续程序读取或报表展示。
- 误用
GROUP BY后再用应用层拼接,增加网络和解析开销 - 若真要分组统计,且状态值不确定(比如动态新增),才考虑
GROUP BY+ 应用端 pivot,但这是另一类需求
WHERE 先过滤再统计,别在 CASE 里硬扛
如果只关心最近 7 天的数据,务必把时间条件放在 WHERE 子句,而不是塞进每个 CASE 的 WHEN 里。
否则数据库无法利用时间字段索引,全表扫描概率大增,尤其数据量上百万后延迟明显。
- 正确写法:
WHERE create_time >= NOW() - INTERVAL 7 DAY,再套CASE - 错误写法:
CASE WHEN status = 'paid' AND create_time >= ... THEN 1 ELSE 0 END - PostgreSQL 中可配合
WHERE status IN ('paid','pending','cancelled')进一步缩小扫描范围
NULL 状态要单独处理
很多业务字段允许为 NULL,但 status = 'xxx' 永远不成立,也不会进 ELSE 0 分支——因为 NULL = 'xxx' 结果是 UNKNOWN,不是 TRUE 或 FALSE。
所以统计 NULL 必须显式用 IS NULL:
SUM(CASE WHEN status IS NULL THEN 1 ELSE 0 END) AS null_count
- 别用
status = NULL,永远返回FALSE - 如果业务上
NULL和某个字符串(如'unknown')等价,先统一转换再统计,避免逻辑歧义 - 某些 ORM(如 Django ORM)生成的 SQL 可能自动包装
IS NULL,但原生 SQL 必须手写
实际写的时候,状态少就手写几个 CASE;状态多且固定,可以脚本生成;状态完全动态,就得换思路——比如用 json_agg 或应用层聚合。但绝大多数后台报表场景,CASE WHEN + SUM 是最稳、最透明、最容易调试的选择。

















