推荐用 sum(case when status = 'success' then 1 else 0 end) 统计多状态数量,语义明确且 null 安全;避免 count(status = 'success') 等错误写法,where 应仅用于业务范围过滤而非状态筛选,务必补 else 0 防空值,状态值需与上游系统严格一致。

用 CASE WHEN + SUM 统计多状态数量
直接在 SELECT 里对同一字段做多次条件聚合,比写多个子查询或 COUNT(IF()) 更清晰、兼容性更好。MySQL、PostgreSQL、SQL Server 都支持这种写法,且执行计划通常更优。
常见错误是试图用 COUNT(status = 'success') 或 COUNT(CASE WHEN ...) —— 这会导致失败记录被忽略(因为 COUNT 只统计非 NULL),必须配合 SUM 或 COUNT 内部返回 1/0 才准确。
-
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END):推荐,语义明确,NULL 安全 -
COUNT(CASE WHEN status = 'success' THEN 1 END):可行,但失败行返回 NULL,COUNT自动跳过,结果等价于成功行数 - 避免
COUNT(status = 'success'):在 MySQL 中虽能运行,但属隐式转换,PostgreSQL 直接报错function count(boolean) does not exist
WHERE 会过滤掉另一状态,别误加
如果加了 WHERE status IN ('success', 'failed'),看起来安全,但实际会排除 status 为 NULL、'pending' 等其他值的行——而这些行本应计入“失败”或需单独处理。统计多状态时,WHERE 应仅用于业务范围过滤(如时间范围),而非状态筛选。
正确做法是把状态判断全部放在 CASE WHEN 内:
SELECT
SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) AS success_cnt,
SUM(CASE WHEN status IN ('failed', 'cancelled', 'timeout') THEN 1 ELSE 0 END) AS failed_cnt
FROM orders
WHERE created_at >= '2024-01-01';
GROUP BY 场景下要小心 NULL 分组
当按日期、渠道等维度分组统计时,若某组内所有订单状态都为 NULL,SUM(CASE ...) 仍返回 0,但 COUNT(CASE ...) 会返回 0(因为没匹配到非 NULL 值)——两者结果一致。真正容易出错的是漏掉 ELSE 0,导致该组对应列返回 NULL,后续计算或导出易引发空值问题。
- 务必写
ELSE 0,不要省略 - 若源表
status允许 NULL,且你希望把 NULL 单独计为一类,可扩展为:CASE WHEN status IS NULL THEN 1 ELSE 0 END - PostgreSQL 中
COALESCE(status, 'unknown')可统一补缺,但注意补值后需同步更新CASE条件
性能敏感时避免重复扫描
单次扫描完成多路统计是最大优势,但若 status 列无索引,而表又很大,CASE WHEN 本身不加速过滤。此时应优先确保 WHERE 条件字段(如 created_at)有索引;status 字段是否建索引,取决于查询中它是否常用于 WHERE 精确过滤——单纯用于 CASE 聚合,索引收益极小。
另外,别为了“看起来简洁”把多个 CASE 合并成一个(比如用 DECODE 或 IF 嵌套),可读性和优化器识别度反而下降。保持一行一状态,维护成本最低。
复杂点在于状态定义可能分散在业务逻辑、字典表甚至代码里,SQL 里硬编码 'success' 'failed' 时,得确认它们和上游系统完全一致——差一个空格或大小写,统计就归零。











