用 case when + sum 实现状态横向分布统计最稳妥,如 sum(case when user_status = 0 then 1 else 0 end) 避免 null 导致漏计,优于 count 和不通用的 pivot。

用 CASE WHEN + SUM 做状态分布计数最稳
直接 GROUP BY user_status 只能出各状态的行数,但业务常要“待支付、已支付、已取消”三列并排展示,这种横向分布必须靠条件聚合。别信某些教程说的 PIVOT,MySQL 8.0 以下压根不支持,SQL Server 的 PIVOT 语法又死板——字段名写死、加个新状态就得改 SQL。
实操建议:
-
CASE WHEN是跨数据库通用解法,SUM(CASE WHEN user_status = 0 THEN 1 ELSE 0 END)比COUNT(CASE WHEN ...)更安全:NULL 不参与计数,避免漏统计 - 枚举值别硬编码在 SQL 里,比如
user_status = 0—— 万一哪天 DBA 把 0 改成 100,所有报表全挂;应该查information_schema.COLUMNS或建字典表关联 - 如果状态值是字符串(如
'pending'),注意大小写和空格:TRIM(UPPER(user_status)) = 'PENDING'
聚合时漏掉 ELSE 0 会导致 NULL 聚合结果
常见错误是写成 SUM(CASE WHEN user_status = 0 THEN 1 END),缺了 ELSE 0。这时候当某行不满足条件,CASE 返回 NULL,而 SUM(NULL, 1, 1) 结果是 2,但 SUM(NULL, NULL, 1) 还是 1——可一旦整组都不匹配,结果就是 NULL,不是 0。BI 工具渲染时 NULL 常显示为空白或报错,排查起来极隐蔽。
正确写法必须带 ELSE 0:
SELECT SUM(CASE WHEN user_status = 0 THEN 1 ELSE 0 END) AS pending_count, SUM(CASE WHEN user_status = 1 THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN user_status = 2 THEN 1 ELSE 0 END) AS cancelled_count FROM orders;
多维交叉时用 GROUPING SETS 避免重复扫描
如果还要按地区、时间再分组统计状态分布,硬写三层嵌套 GROUP BY + CASE 会慢且难维护。PostgreSQL 和 SQL Server 支持 GROUPING SETS,一条语句产出多个粒度的结果:
SELECT region, order_month, SUM(CASE WHEN user_status = 0 THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN user_status = 1 THEN 1 ELSE 0 END) AS paid FROM orders GROUP BY GROUPING SETS ((region, order_month), (region), ());
上面语句一次返回:按地区+月份的明细、按地区的汇总、全局总计。MySQL 不支持,只能用 UNION ALL 拼接,但要注意各子查询字段顺序和类型必须严格一致。
JSON 化输出适合前端动态渲染
当状态种类不固定(比如运营临时加个“人工审核中”),或者前端要自己决定怎么画柱状图,硬写 N 个 CASE 列就不现实。这时用 JSON_OBJECTAGG(MySQL 5.7+ / PostgreSQL)把分布打成键值对:
SELECT
region,
JSON_OBJECTAGG(
status_label,
cnt
) AS status_distribution
FROM (
SELECT
region,
CASE user_status
WHEN 0 THEN 'pending'
WHEN 1 THEN 'paid'
WHEN 2 THEN 'cancelled'
END AS status_label,
COUNT(*) AS cnt
FROM orders
GROUP BY region, user_status
) t
GROUP BY region;
结果形如 {"pending": 120, "paid": 890, "cancelled": 45},前端直接 JSON.parse() 就能遍历。注意:别在大表上滥用,JSON_OBJECTAGG 是聚合函数,性能开销比纯数值聚合高 20%~30%。
真正麻烦的是枚举值和业务含义脱节——比如数据库里存 1,但产品文档写的是“处理中”,开发又当成“已发货”。这种问题光靠 SQL 解不了,得推动建状态字典表并加外键约束。










