CASE WHEN 无法直接完成区间统计,因为它仅做行级条件映射、不生成新行也不聚合数据;必须配合子查询或CTE先打标签,再外层GROUP BY分组计数。

为什么直接用 CASE WHEN 无法完成区间统计?
因为 CASE WHEN 本身不产生新行、也不聚合数据,它只是对已有行做条件映射。当你想统计「每个收入区间有多少用户」,本质是分组计数,而 CASE WHEN 单独写在 SELECT 里,若没配合 GROUP BY 或子查询,结果只会按原表行数展开,无法归并区间。
正确做法:CASE WHEN 必须嵌在子查询或 CTE 中再聚合
典型结构是先用子查询把原始字段转为区间标签,外层再按标签分组统计。比如统计用户收入分布:
SELECT income_range, COUNT(*) AS user_count
FROM (
SELECT
CASE
WHEN salary
-
BETWEEN包含边界,注意和/>=的开闭一致性 - 子查询别名
t在大多数数据库(MySQL 8.0+、PostgreSQL、SQL Server)中不可省略 - 如果原表有
NULL的salary,CASE不会匹配任何分支,默认返回NULL,该行会被计入income_range IS NULL组——需显式加WHEN salary IS NULL THEN '未知'
当区间逻辑复杂时,避免在多层 CASE 中重复写子查询
比如要按「近30天登录次数 + 当前会员等级」联合分区间,子查询里嵌套另一个子查询易读性差。此时应优先用 CTE:
WITH login_stats AS (
SELECT user_id, COUNT(*) AS recent_logins
FROM user_logs
WHERE log_time >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
),
user_with_level AS (
SELECT u.id, u.level, COALESCE(l.recent_logins, 0) AS logins
FROM users u
LEFT JOIN login_stats l ON u.id = l.user_id
)
SELECT
CASE
WHEN level = 'VIP' AND logins >= 5 THEN '活跃VIP'
WHEN level = 'VIP' THEN '沉默VIP'
WHEN logins >= 3 THEN '普通活跃'
ELSE '低频用户'
END AS segment,
COUNT(*)
FROM user_with_level
GROUP BY segment;
- CTE 比嵌套子查询更易调试:可单独运行
SELECT * FROM login_stats验证中间结果 -
COALESCE处理左连接产生的NULL,否则CASE中logins >= 5对NULL返回UNKNOWN,不命中任何分支 - MySQL 5.7 不支持 CTE,此时必须用两层子查询,且外层不能引用内层别名(需全部展平)
性能隐患:WHERE 条件不能下推到 CASE 子查询内部
如果你只关心「VIP 用户的区间分布」,错误写法是:
SELECT income_range, COUNT(*) FROM (SELECT CASE ... END AS income_range FROM users) t WHERE t.income_range IS NOT NULL; -- ❌ 过滤发生在聚合后,全表已扫描
正确做法是把过滤提前到子查询里:
SELECT income_range, COUNT(*) FROM ( SELECT CASE ... END AS income_range FROM users WHERE status = 'active' AND level = 'VIP' -- ✅ 提前过滤 ) t GROUP BY income_range;
- 尤其当原表上亿行时,漏掉这一步可能导致临时表膨胀、内存溢出
- 某些数据库(如 Hive)对子查询中
CASE字段无法走索引,务必在子查询WHERE中使用原始列(如salary)而非别名
区间统计真正卡住人的地方,往往不是语法写不对,而是没想清楚「哪一层该过滤、哪一层该分组、哪一层该补 NULL」。尤其是跨时间窗口+多维标签的场景,先拆 CTE 再拼 CASE,比硬套三层子查询可靠得多。










