应统一使用sum(case when then 1 else 0 end)实现多状态计数,因其逻辑清晰、null处理明确、兼容所有主流数据库,且避免count(if())的null陷阱和pivot的跨库不支持问题。

用 CASE WHEN + SUM 实现状态计数行转列
直接在 SELECT 中对每个状态写一个 CASE WHEN 分支,再套一层 SUM(或 COUNT),就能把多行状态数据压到一行里。这是最通用、兼容性最好的方式,MySQL、PostgreSQL、SQL Server、Oracle 都支持。
常见错误是漏掉 ELSE 0 —— 如果某状态完全没出现,CASE 返回 NULL,SUM 会跳过它,导致该列结果为 NULL 而非 0,前端展示或后续计算容易出错。
SELECT SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) AS approved_count, SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) AS rejected_count FROM orders;
- 必须用
SUM或COUNT聚合,否则会按原行数返回多行 -
ELSE 0不可省;用COUNT时要写COUNT(CASE WHEN ... THEN 1 END),因为COUNT忽略 NULL - 如果字段本身可能为 NULL(比如
status允许为空),需额外判断status IS NOT NULL
MySQL 8.0+ 可用 COUNT IF 简化写法
MySQL 8.0 起支持 COUNT() OVER() 和更简洁的 COUNT(IF(...)),但注意:这不是标准 SQL,换数据库就失效。
实际中很多人误以为 IF 是函数调用,其实它是 MySQL 特有的控制流函数,括号内必须是三个参数:IF(condition, true_value, false_value)。少写一个就会报错 Incorrect parameter count in the call to native function 'if'。
SELECT COUNT(IF(status = 'pending', 1, NULL)) AS pending_count, COUNT(IF(status = 'approved', 1, NULL)) AS approved_count, COUNT(IF(status = 'rejected', 1, NULL)) AS rejected_count FROM orders;
-
COUNT(IF(...))依赖IF返回 NULL 来跳过计数,所以第三参数必须是NULL,不能写0 - 和
SUM(CASE...)比,语义稍弱,可读性差一点,但字符更少 - 低版本 MySQL(如 5.7)不支持,会直接报错
FUNCTION db.IF does not exist
避免用 PIVOT(除非你确定用的是 SQL Server 或 Oracle)
PIVOT 看起来更“正规”,但它只在 SQL Server 和 Oracle 中原生支持,PostgreSQL 和 MySQL 完全不认这个关键字——执行会直接报错 ERROR: syntax error at or near "PIVOT" 或 Unknown column 'PIVOT' in 'field list'。
即使在 SQL Server 中,PIVOT 也要求聚合函数显式指定,且列名必须是确定的字面量,没法动态适配新状态值。一旦业务加了 'processing' 状态,SQL 就得手动改,维护成本高。
- SQL Server 示例中,
FOR status IN ([pending], [approved], [rejected])的方括号是必需语法,漏掉会报错 - Oracle 的
PIVOT要求子查询别名,且不支持表达式,status || '_count'这种拼接列名会失败 - 想兼容多库?老实用
CASE WHEN,别贪图语法糖
GROUP BY 错误:忘了加聚合或加错了维度
如果原始表有用户 ID、订单时间等字段,又想按用户统计各状态数量,很容易随手写 GROUP BY user_id 却忘了所有非聚合字段都得进 GROUP BY,或者漏掉 SUM 导致 SQL 报错。
典型错误信息包括:ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause(MySQL 严格模式),或 PostgreSQL 的 column must appear in the GROUP BY clause or be used in an aggregate function。
SELECT user_id, SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END) AS approved_count FROM orders GROUP BY user_id;
- 只要用了聚合函数(
SUM、COUNT等),SELECT 中所有其他字段都必须出现在GROUP BY中 - 如果还想要最新订单时间,不能直接写
MAX(created_at)就完事——得确认这是否符合业务语义;有时需要先子查询取每用户最新记录,再统计状态分布 - 大数据量下,
CASE WHEN在 WHERE 之后执行,无法利用status索引加速;必要时可建函数索引或冗余计数字段
PIVOT 或 IF 的局限性。动手前先查下 SELECT DISTINCT status FROM orders,确保你知道有哪些值要覆盖。










