直接用 GROUP BY 无法处理非连续区间,因其仅支持等值分组,而需用 CASE WHEN 显式映射各区间为标签;CASE WHEN 应按序排列、端点明确(如 BETWEEN a AND b),避免遗漏、重叠或错配。

为什么直接用 GROUP BY 无法处理非连续区间
因为 GROUP BY 只能按字段原始值或表达式结果做等值分组,而“非连续区间”(比如 0–10、20–30、50–60)之间存在空隙,没法靠 ROUND() 或 FLOOR(col / 10) 这类连续映射函数覆盖。一旦硬套,20 会被分进 “20–29” 组,但你实际想把它和 25 一起归入“20–30”这个业务定义的独立区间——这时候必须先用 CASE WHEN 把每行映射到一个可分组的标签上。
CASE WHEN 分区逻辑怎么写才不漏、不错、不重叠
关键在条件顺序和边界处理。SQL 中 CASE WHEN 是从上到下匹配,第一个为真就返回,后续忽略。所以区间必须按顺序排列,且明确包含端点:
- 用
BETWEEN a AND b表示闭区间(含 a 和 b),比>= a AND 更简洁不易错 - 如果区间不相邻(如 0–10、20–30),中间值(如 15)会掉进
ELSE,要显式处理成NULL或统一归为'other',否则GROUP BY会把所有NULL拢成一组,可能掩盖数据异常 - 避免用
和 <code> 混搭写半开区间(如 <code>col >= 0 AND col ),虽然可行,但多人协作时容易看错边界归属
示例:将销售额划分为业务定义的非连续档位
SELECT
CASE
WHEN sales BETWEEN 0 AND 1000 THEN '入门'
WHEN sales BETWEEN 2000 AND 5000 THEN '成长'
WHEN sales BETWEEN 8000 AND 10000 THEN '高产'
ELSE '未分类'
END AS level,
COUNT(*) AS cnt,
AVG(sales) AS avg_sales
FROM orders
GROUP BY
CASE
WHEN sales BETWEEN 0 AND 1000 THEN '入门'
WHEN sales BETWEEN 2000 AND 5000 THEN '成长'
WHEN sales BETWEEN 8000 AND 10000 THEN '高产'
ELSE '未分类'
END;
GROUP BY 里重复写 CASE WHEN 很麻烦,能复用吗
不能直接用列别名(如 GROUP BY level),多数数据库(PostgreSQL、SQL Server、Oracle)不支持在 GROUP BY 中引用 SELECT 列别名;MySQL 8.0+ 虽支持,但为兼容性和可读性,建议统一写法:
- 用子查询或 CTE 提前算好分类字段,外层再
GROUP BY,逻辑清晰也方便调试 - 某些引擎(如 Spark SQL、Doris)支持
GROUP BY 1按位置引用,但可读性差,上线前务必确认执行计划是否走索引 - 如果只是简单统计,且不想嵌套,复制一遍
CASE WHEN最稳妥——别为了少打几行字引入歧义
性能和 NULL 值容易被忽略的细节
CASE WHEN 本身不走索引,所以如果原表很大、又只查某几个区间,先加 WHERE 过滤再分组,比全表跑 CASE 快得多。另外注意:
-
sales IS NULL不会命中任何BETWEEN分支,直接进ELSE,如果业务上 NULL 有特殊含义(比如未录单),得单独加WHEN sales IS NULL THEN '缺失' - 字符串区间(如版本号
'v1.2'、'v2.10')不能用BETWEEN,得转成数字或用REGEXP,否则'v2.10' < 'v2.2'会为真 - 浮点字段(如
price)用BETWEEN要小心精度,建议先ROUND(price, 2)再判断
非连续区间的本质是业务规则映射,不是数学划分——写清楚每个分支的业务含义,比追求语法多短更重要。

















