CASE WHEN配合聚合函数可单次扫描完成多条件统计,避免重复读表;需在每个聚合内用CASE WHEN分支过滤,注意NULL处理及数据库兼容性。

用 CASE WHEN + 聚合函数实现单次扫描多条件统计
不需要写多个子查询或 UNION,CASE WHEN 是最直接、性能也最好的方式。它让一次全表扫描就能产出多个条件下的聚合值,避免重复读表和连接开销。
常见错误是把多个 COUNT(*) 直接并列写,却忘了加条件过滤——结果全是总行数。正确做法是每个聚合函数内部用 CASE WHEN 做条件分支:
SELECT COUNT(*) AS total, COUNT(CASE WHEN status = 'active' THEN 1 END) AS active_count, SUM(CASE WHEN amount > 100 THEN amount ELSE 0 END) AS high_amount_sum, AVG(CASE WHEN created_at >= '2024-01-01' THEN score END) AS recent_avg_score FROM orders;
-
CASE WHEN分支中返回具体值(如1或字段),否则返回NULL;聚合函数会自动忽略NULL -
AVG和SUM对空分支返回NULL,不是0,注意业务是否需用COALESCE处理 - MySQL 8.0+、PostgreSQL、SQL Server 都支持;SQLite 支持但不优化窗口函数场景
WHERE 和 HAVING 混用时聚合条件容易错位
WHERE 过滤行,HAVING 过滤分组后结果,两者作用阶段不同。想查“每个部门里高薪员工数 + 平均薪资”,不能把薪资条件写在 WHERE 里再套 HAVING——那会先筛掉所有人,导致分组为空。
正确逻辑是:先按部门分组,再在每组内用 CASE WHEN 统计符合条件的人数:
SELECT dept, COUNT(*) AS total_emp, COUNT(CASE WHEN salary > 15000 THEN 1 END) AS high_salary_count, AVG(salary) AS avg_salary FROM employees GROUP BY dept;
- 如果真要用
HAVING,只能用于过滤分组结果,比如HAVING COUNT(*) > 5,不能用来替代条件聚合 -
WHERE salary > 15000会先剔除低薪员工,后续COUNT(*)就不再是部门总人数了 - 涉及时间范围、状态码等离散条件时,优先考虑
CASE WHEN而非拆成多个WHERE查询
多个 COUNT(DISTINCT) 同时计算的性能陷阱
当需要对不同条件分别做去重计数(比如“活跃用户数”“付费用户数”“iOS 用户数”),直接写多个 COUNT(DISTINCT user_id) 会让数据库反复扫描并去重,尤其在大表上非常慢。
更高效的方式是用 COUNT(DISTINCT ...) 配合 CASE WHEN,但注意语法限制:
SELECT COUNT(DISTINCT user_id) AS all_users, COUNT(DISTINCT CASE WHEN status = 'paid' THEN user_id END) AS paid_users, COUNT(DISTINCT CASE WHEN os = 'ios' THEN user_id END) AS ios_users FROM events;
- PostgreSQL 和 MySQL 8.0+ 支持这种写法;旧版 MySQL 会报错,需改用子查询或临时表
-
COUNT(DISTINCT CASE ...)中的CASE必须返回可比较类型,不能是表达式组合(如CASE WHEN a=1 THEN b||c END在某些引擎里不被允许) - 如果字段本身含
NULL,COUNT(DISTINCT)会自动跳过,无需额外处理
用 FILTER 子句替代 CASE WHEN(PostgreSQL 9.4+)
PostgreSQL 提供了更语义清晰的 FILTER 语法,功能等价于 CASE WHEN,但可读性更好、不易写错:
SELECT COUNT(*) FILTER (WHERE status = 'active') AS active_count, SUM(amount) FILTER (WHERE amount > 100) AS high_amount_sum, AVG(score) FILTER (WHERE created_at >= '2024-01-01') AS recent_avg_score FROM orders;
-
FILTER只在 PostgreSQL 中可用,MySQL / SQL Server 不支持,跨库迁移时要注意 - 不能嵌套使用,比如
COUNT(*) FILTER (WHERE x > 0) FILTER (WHERE y = 1)是非法的 - 和
CASE WHEN一样,不满足条件时视为NULL,不影响其他聚合项
真正麻烦的是混合条件之间存在依赖关系,比如“近7天下单且完成支付的用户数”,这时候 CASE WHEN 的布尔组合要写清楚优先级,别漏掉括号。

















