直接用 group by + 多个 count 不行,因 case when 在聚合中先逐行计算再统计非 null 值;误写 else 0 或布尔表达式会导致 count 统计 0 或报错,应优先用 count(case when ... then 1 end) 统计满足条件的记录数。

为什么直接用 GROUP BY + 多个 COUNT 不行
很多人写 COUNT(*)、COUNT(CASE WHEN ...) 时发现结果全为 0 或数值异常,根本原因是没理解 CASE WHEN 在聚合函数里的执行时机:它是在每行上先计算分支结果,再交给 COUNT 统计非 NULL 值。如果写成 COUNT(status = 'done')(MySQL 风格布尔转整数)或漏掉 ELSE NULL,就可能把 0 当作有效计数,导致总数虚高。
-
COUNT()只统计非 NULL 值,不是“统计真值”,所以CASE WHEN status='done' THEN 1 END是安全的;但CASE WHEN status='done' THEN 1 ELSE 0 END会让COUNT把所有 0 也当有效项计——此时该用SUM() - PostgreSQL 对
BOOLEAN类型不隐式转整数,COUNT(status = 'done')直接报错;必须显式用SUM(CASE WHEN ... THEN 1 ELSE 0 END) - Oracle 中
COUNT忽略 NULL,但DECODE或CASE若没写ELSE,默认补 NULL —— 这反而是对的,别手贱加ELSE 0
CASE WHEN 放在 COUNT 里还是 SUM 里
取决于你要的是“满足条件的记录数”还是“带权重的汇总”。绝大多数分组统计场景要的是前者,所以优先用 COUNT(CASE WHEN ... THEN 1 END);只有需要加权求和(比如按等级乘系数)才用 SUM。
- 统计“已完成订单数”:
COUNT(CASE WHEN order_status = 'shipped' THEN 1 END) - 统计“订单金额总和,仅限 VIP 客户”:
SUM(CASE WHEN is_vip = 1 THEN amount ELSE 0 END)(注意这里用ELSE 0,因为SUM需要数值,NULL 会跳过) - 混用时别套错:想算“VIP 客户平均下单金额”,不能写
AVG(CASE WHEN is_vip=1 THEN amount END)—— 这会把非 VIP 的amount当 NULL 排除,但分母仍是全部记录数;正确写法是先过滤或用条件聚合配合COUNT/SUM手动算
避免 WHERE 提前过滤导致分组失真
如果先用 WHERE status IN ('done', 'pending'),那其他状态(如 'canceled')就彻底从结果里消失了,无法体现“各状态占比”。真正要做多维对比,得把所有状态保留在 GROUP BY 范围内,靠 CASE WHEN 在聚合层做切片。
- 错误:用
WHERE type = 'sale'后再统计 sale/rent 比例 → 没有 rent 数据可比 - 正确:去掉
WHERE,改用SUM(CASE WHEN type='sale' THEN 1 ELSE 0 END)和SUM(CASE WHEN type='rent' THEN 1 ELSE 0 END)同时出现在 SELECT 中 - 性能提示:大表上这种写法不会比加
WHERE慢多少,因为引擎仍可走索引扫描;但若CASE条件涉及函数(如UPPER(name)),就可能拖慢,尽量用原字段判断
兼容性陷阱:NULL 处理与数据库方言差异
同一个 CASE WHEN 表达式,在 MySQL、PostgreSQL、SQL Server 里行为基本一致,但 NULL 的“可见性”容易被忽略——尤其当字段本身允许 NULL 时。
- 假设
region字段有 NULL 值,COUNT(CASE WHEN region = 'CN' THEN 1 END)不会统计这些 NULL 行;但COUNT(CASE WHEN region IS NULL THEN 1 END)才能捕获它们 - SQL Server 中
LEN(NULL)返回 NULL,所以CASE WHEN LEN(name) > 5 THEN 'long' END对空字符串和 NULL 都返回 NULL;需显式写WHEN name IS NOT NULL AND LEN(name) > 5 - 别依赖
ELSE的默认行为:有些旧版 SQLite 把没写ELSE的CASE默认补 0,而标准 SQL 要求补 NULL;统一写ELSE NULL最稳妥
CASE WHEN 分支静默失效。










