直接用 COUNT(DISTINCT CASE WHEN...) 会出错,是因为未匹配条件时 CASE 返回 NULL,而 COUNT(DISTINCT) 自动忽略 NULL,导致有效值被漏计;正确写法是仅让满足条件的值透出、其余返回 NULL。

为什么直接用 COUNT(DISTINCT CASE WHEN ...) 会出错
很多人写 COUNT(DISTINCT CASE WHEN condition THEN value END) 时发现结果比预期少,甚至为 NULL。这是因为 DISTINCT 会对 CASE 的全部结果去重,而 CASE 没有匹配分支时默认返回 NULL,NULL 参与 DISTINCT 会被忽略(SQL 标准中 NULL 不参与去重比较),导致本该计数的非空值被“稀释”。
COUNT(DISTINCT ...) + CASE WHEN 的正确写法
核心思路:先用 CASE WHEN 过滤出目标值,再对非 NULL 值做 DISTINCT 计数。关键在于让不满足条件的行返回 NULL,且确保满足条件的值本身不含 NULL(否则也会被跳过)。
- ✅ 正确:把条件逻辑放在
CASE内部,只让符合条件的值透出,其余为NULL - ❌ 错误:在
CASE外套一层WHERE或试图用ELSE 0——0是有效值,会污染去重结果 - ⚠️ 注意:
COUNT(DISTINCT ...)会自动忽略所有NULL,所以无需额外WHERE过滤
示例(统计每个部门中 salary > 5000 的员工去重职级数):
SELECT dept, COUNT(DISTINCT CASE WHEN salary > 5000 THEN job_title END) AS distinct_high_salary_jobs FROM employees GROUP BY dept;
替代方案:FILTER 子句(PostgreSQL / SQL:2003 标准)
如果你用的是 PostgreSQL(10+)、Snowflake 或支持 FILTER 的引擎,它比 CASE 更直观、更安全,且语义明确——只对满足条件的行应用聚合函数。
-
FILTER不会产生NULL中间值,无歧义 - 不依赖
CASE的隐式NULL行为,可读性更强 - MySQL 和 SQL Server 目前不支持,需降级为
CASE方案
等价写法(PostgreSQL):
SELECT dept, COUNT(DISTINCT job_title) FILTER (WHERE salary > 5000) AS distinct_high_salary_jobs FROM employees GROUP BY dept;
容易被忽略的性能与数据陷阱
COUNT(DISTINCT ...) 本身是高开销操作,嵌套 CASE 后不会额外增加计算量,但容易掩盖两个真实问题:
- 如果
job_title字段本身含NULL,即使salary > 5000成立,该行也不会被计入——因为DISTINCT NULL被跳过;需确认业务上NULL是否代表有效状态 - 大数据量下,
COUNT(DISTINCT)可能触发磁盘临时表或 OOM,部分数据库(如 Hive)对CASE内表达式不走索引,建议在salary和job_title上建组合索引 - MySQL 5.7 对
COUNT(DISTINCT CASE...)有已知优化缺陷,8.0+ 改进明显,旧版本建议先用子查询预过滤
真正难的不是语法,而是想清楚:你到底要数「满足条件的去重值」,还是「满足条件的记录中,某个字段的去重值」——后者才是绝大多数人实际需要的,而前者容易因 NULL 语义翻车。










