用CASE WHEN+GROUP BY实现区间频次统计,需先将连续数值映射为离散标签再分组;必须显式写ELSE防NULL遗漏,边界推荐左闭右开,MySQL可用FLOOR模拟等宽分桶。

用 CASE WHEN + GROUP BY 实现区间频次统计
直接在 GROUP BY 里写区间表达式会报错,必须先把数值映射成离散标签,再分组。核心思路是用 CASE WHEN 把连续数值转为分类字段(比如 “0-10”、“11-20”),然后对这个字段 GROUP BY 计数。
常见错误是漏写 ELSE,导致 NULL 值被忽略或归入意外分组;另外边界值处理容易出错(比如 10 算哪一组)。
SELECT
CASE
WHEN score < 60 THEN '不及格'
WHEN score < 80 THEN '良好'
WHEN score <= 100 THEN '优秀'
ELSE '异常'
END AS level,
COUNT(*) AS cnt
FROM exam_results
GROUP BY level;
- 每个
WHEN条件要覆盖互斥、连续的范围,推荐用“左闭右开”习惯(如、<code>>= 60 AND )避免重叠 -
ELSE必须显式写出,否则 NULL 或越界值不会出现在结果中 - 别在
GROUP BY里重复写整个CASE表达式——用列别名level更安全,也更易读
用 WIDTH_BUCKET 快速划分等宽区间(Oracle / PostgreSQL)
如果只需要等宽分桶(比如每 5 分一个桶),WIDTH_BUCKET 比手写 CASE 更简洁、不易错,且自动处理边界和溢出。
它返回的是整数桶编号(从 1 开始),不是字符串标签,后续可连表或用 DECODE/CASE 转义。
SELECT WIDTH_BUCKET(score, 0, 100, 20) AS bucket_id, -- 0~100 分成 20 个等宽桶 COUNT(*) AS cnt FROM exam_results GROUP BY bucket_id ORDER BY bucket_id;
- 第三个参数是桶数量,不是桶宽度;桶宽度 = (max - min) / n
- 值小于下界 → 返回 0;大于上界 → 返回 n+1;这两个溢出桶需单独
WHERE过滤或CASE处理 - MySQL 不支持该函数,得退回
CASE方案
MySQL 中模拟 WIDTH_BUCKET 的安全写法
MySQL 没有内置分桶函数,但可以用数学运算 + FLOOR 替代,关键是把除法结果偏移归零,再加 1 对齐桶编号。
错误写法如 FLOOR(score / 10) 会把 0~9 映射到 0,而业务通常希望第一桶是 1,且要防负数或空值。
SELECT FLOOR((score - 0) / 10) + 1 AS bucket_id, COUNT(*) AS cnt FROM exam_results WHERE score IS NOT NULL AND score BETWEEN 0 AND 100 GROUP BY bucket_id ORDER BY bucket_id;
- 减去最小值(这里是 0)再除以桶宽(10),确保从 0 开始计数,+1 后变成 1~10
-
WHERE过滤掉 NULL 和异常值,比让FLOOR返回负数或 NULL 更可控 - 如果原始数据含负数,必须先
COALESCE(score, 0)或按需截断,否则FLOOR(-3.2)= -4,桶号错乱
性能与可维护性提醒
区间逻辑写在 SQL 里看似方便,但一旦规则变(比如考试标准从百分制改五级制),所有相关查询都得改。生产环境建议把区间定义抽成配置表或视图。
另外,大表上做 CASE 分组不走索引,如果只是查某几个固定区间,用 UNION ALL 分拆 + 索引字段过滤,往往比单条 GROUP BY 更快。
最常被忽略的是时区、精度和 NULL 处理——比如 score DECIMAL(5,2) 和 INT 在边界计算时表现不同,ROUND() 是否介入也要提前确认。

















