count(case when...)不能直接去重,因其仅逐行生成非null值并计数,不消除重复;需配合distinct(如count(distinct case when...then user_id end))或子查询实现按用户等粒度的去重统计。

为什么 COUNT(CASE WHEN ...) 不能直接去重?
因为 COUNT 统计的是非 NULL 值的行数,而 CASE WHEN 只是逐行生成值,并不改变行本身。如果你写 COUNT(CASE WHEN status = 'paid' THEN user_id END),它会把每个匹配的 user_id 都算一次——哪怕同一个 user_id 出现了 5 次,就计 5 次,不是 1 次。
真正需要去重统计时,得让每组只贡献一个“有效标记”,常见做法是先用 DISTINCT 或聚合前预处理,再套 CASE WHEN。
COUNT(DISTINCT CASE WHEN ...) 的正确写法与限制
多数主流数据库(PostgreSQL、MySQL 8.0+、SQL Server)支持 COUNT(DISTINCT ...) 中嵌套 CASE WHEN,但注意:只有当 CASE 的结果列本身可去重(比如主键、唯一标识)时才有意义;如果返回重复值,DISTINCT 会生效,但逻辑可能不符合预期。
- ✅ 正确(按用户去重统计已付款人数):
COUNT(DISTINCT CASE WHEN status = 'paid' THEN user_id END)
- ❌ 错误(用订单 ID 去重但想统计用户):
COUNT(DISTINCT CASE WHEN status = 'paid' THEN order_id END)
——这统计的是付款订单数,不是用户数 - ⚠️ 注意:SQLite 不支持
COUNT(DISTINCT CASE ...),会报错near "CASE": syntax error,得改用子查询
兼容 SQLite 或需复杂条件时,用子查询替代
当目标字段不能直接用于 DISTINCT,或数据库不支持该语法时,把去重逻辑提前到子查询里更可控。
- 先筛选出符合条件的唯一用户:
SELECT COUNT(*) FROM (SELECT DISTINCT user_id FROM orders WHERE status = 'paid') AS t
- 要同时统计多个条件(如付费用户数、未付款用户数、总用户数),用
LEFT JOIN或UNION ALL拆开再聚合,比硬塞进一个CASE更清晰 - 避免在
CASE中返回NULL和非空混合后套DISTINCT——例如CASE WHEN x THEN y ELSE NULL END是安全的;但CASE WHEN x THEN y ELSE z END若y和z有重叠值,DISTINCT会误合并
性能和索引注意事项
COUNT(DISTINCT ...) 在大数据量下通常比普通 COUNT(*) 慢得多,尤其当 CASE 表达式无法利用索引时。
- 确保
WHERE条件中的字段(如status)和CASE中的分组字段(如user_id)有联合索引,例如:CREATE INDEX idx_status_uid ON orders (status, user_id)
- MySQL 5.7 对
COUNT(DISTINCT CASE...)优化较差,实测比子查询慢 3–5 倍;建议在慢查询场景中优先压测子查询方案 - PostgreSQL 中若
user_id本身是主键,COUNT(DISTINCT CASE...)能走 HashAgg,效率尚可;但若user_id是字符串且长度大,内存开销会上升
DISTINCT,而不是习惯性往 CASE 里塞所有看起来相关的列。











