不能直接用 COUNT(条件字段),因为 COUNT() 统计非 NULL 值个数,而 status = 'done' 返回 0/1(均非 NULL),导致结果为总行数;正确写法是 COUNT(CASE WHEN status = 'done' THEN 1 END),可跨数据库兼容且精准计数满足条件的行。

为什么不能直接用 COUNT(条件字段)
因为 COUNT() 统计的是非 NULL 值的个数,不是“满足条件”的个数。如果你写 COUNT(status = 'done'),MySQL 会把它当做一个布尔表达式,结果是 0 或 1 —— 但 COUNT() 仍会把所有 0 和 1 都算进去(只要不为 NULL),最终得到总行数,而不是“done”的数量。
常见错误现象:
– 分组后每组的统计值全是该组总行数
– 用 SUM(status = 'done') 在 MySQL 里能凑合,但在 PostgreSQL 或 SQL Server 会报错
- 正确做法是让条件只产生 NULL 或非 NULL,再交给
COUNT():用CASE WHEN status = 'done' THEN 1 END -
COUNT()对CASE的返回值做统计时,自动忽略 NULL,只计数非 NULL 行 - 别用
COUNT(1)或COUNT(*)套在CASE外面——那又变回总数了
COUNT(CASE WHEN ...) 的标准写法
这是跨数据库最稳的写法,PostgreSQL、MySQL、SQL Server、Oracle 全支持。
SELECT user_id,
COUNT(*) AS total,
COUNT(CASE WHEN status = 'done' THEN 1 END) AS done_count,
COUNT(CASE WHEN status IN ('pending', 'retry') THEN 1 END) AS pending_or_retry
FROM orders
GROUP BY user_id;-
CASE WHEN ... THEN 1 END中的END后面**不能加 ELSE 0**,否则 0 是非 NULL 值,会被COUNT()算进去 - 想统计“不满足条件”的数量?用
COUNT(CASE WHEN NOT status = 'done' THEN 1 END),别用COUNT(*) - done_count—— 如果有 NULL 状态,结果会不准 - 多个条件嵌套?可以,但优先拆成多个独立
CASE字段,比用ELSE CASE更易读、易维护
替代方案:SUM + 布尔转整型(慎用)
MySQL 和 PostgreSQL 支持把布尔表达式直接转成 0/1,用 SUM() 累加:
SELECT user_id,
SUM((status = 'done')::int) AS done_count -- PG 写法
FROM orders
GROUP BY user_id;但这个写法不通用:
- MySQL 里
SUM(status = 'done')可行(隐式转 int),但 SQL Server 报错:“无法对布尔值求和” - PostgreSQL 必须显式加
::int或CAST(... AS int),少一个字符就失败 - 语义上不如
COUNT(CASE...)直观——SUM是求和,不是计数;遇到空组或全 NULL 时行为也略有差异
性能和 NULL 处理的坑
很多人以为加了 WHERE status IS NOT NULL 就能避免问题,其实不行 —— 分组统计的是“每组内满足条件的行”,过滤掉 NULL 可能导致漏掉本该计入其他条件的行。
- 如果原始数据里
status有 NULL,而你想统计“非 done 的数量”,COUNT(CASE WHEN status != 'done' THEN 1 END)不会包含 NULL 行(因为NULL != 'done'结果是 UNKNOWN,不是 TRUE) - 需要把 NULL 单独考虑?得写成
COUNT(CASE WHEN status != 'done' OR status IS NULL THEN 1 END) - 大数据量下,
CASE WHEN本身几乎没有额外开销,但别在CASE里调子查询或函数(比如CASE WHEN get_category(id) = 'A' THEN 1 END),这会让执行计划变差
最常被忽略的一点:分组字段和条件字段的 NULL 值处理逻辑要一致,否则同一组里有的行进了这个 CASE,有的进了那个,统计口径就乱了。

















