count(distinct case when ...) 返回0,是因为case未命中时默认返回null,而count(distinct)完全忽略null值;若全组均未命中,结果即为0。

为什么直接写 COUNT(DISTINCT CASE WHEN ...) 会返回 0?
因为 CASE WHEN 表达式在条件不满足时默认返回 NULL,而 COUNT(DISTINCT column) 会完全忽略所有 NULL 值——既不参与去重,也不计入结果。如果某组里所有 CASE 都没命中,整列就全是 NULL,最终计数就是 0,不是“没数据”,而是“没非空值”。
COUNT(DISTINCT CASE WHEN ...) 的正确写法
核心是让不满足条件的分支显式返回 NULL(这是必须的),同时确保满足条件时返回的是你要去重的字段本身(比如 user_id),而不是常量或字符串。
- ✅ 正确:
COUNT(DISTINCT CASE WHEN status = 'paid' THEN user_id END) - ❌ 错误:
COUNT(DISTINCT CASE WHEN status = 'paid' THEN 1 END)—— 所有匹配行都变成 1,去重后永远最多是 1 - ❌ 错误:
COUNT(DISTINCT CASE WHEN status = 'paid' THEN user_id ELSE 'N/A' END)—— 把NULL换成字符串,导致本该被忽略的无效行也被算进去了
多条件并行去重统计,一条 SQL 能写几个?
可以一次性写多个 COUNT(DISTINCT CASE ...),数据库会在一次扫描中完成全部去重,比用子查询或 UNION 快得多。但要注意:
- 每个
CASE必须作用于同一粒度字段(如都是user_id),否则语义混乱 - MySQL 8.0+、PostgreSQL、SQL Server 2017+、BigQuery 全部支持;SQLite 和旧版 SQL Server 不支持多列
DISTINCT,但单列CASE没问题 - 如果字段本身含大量
NULL,且你希望把NULL当作一种有效取值,得先用COALESCE(user_id, -1)或类似方式转换
性能陷阱:别在大表上无索引字段硬套
COUNT(DISTINCT ...) 本质要构建哈希表或排序去重,若字段没索引、基数又高(比如 email),全表扫描 + 内存膨胀几乎不可避免。
- 优先给去重字段建索引——B-tree 索引对
COUNT(DISTINCT)加速有限,但能帮优化器跳过部分数据块 - 查前先
EXPLAIN:MySQL 中看到Using temporary; Using filesort就说明正在建临时表;PostgreSQL 中看是否用了HashAggregate且输入行数远超预期 - 数据量超百万?考虑用
APPROX_COUNT_DISTINCT()(BigQuery/Trino)或#(SELECT array_agg(DISTINCT x) FROM t)(PostgreSQL)换精度保响应
CASE 逻辑是否真的覆盖了业务定义的“有效范围”——比如把 status IN ('paid', 'shipped') 写成 = 'paid',漏掉的那部分用户就永远不算进去了,而错误结果看起来还很“合理”。










