正确写法是用 CASE WHEN 定义闭区间,如 WHEN age BETWEEN 18 AND 35,再 GROUP BY 分组;常见错误是边界条件遗漏等号或逻辑混乱,导致数据漏统或重复。

用 CASE WHEN + GROUP BY 实现数值区间分组
直接在 SELECT 里用 CASE WHEN 定义区间,再配合 GROUP BY 即可。这不是窗口函数或子查询的场景,不需要嵌套,关键在于区间边界是否包含端点——多数人漏掉这点导致统计偏差。
常见错误是写成 WHEN score >= 60 AND score ,但没处理 <code>NULL 或超出范围的值,结果漏掉数据或归入“其他”组却没声明。
- 区间必须互斥且覆盖全集(或显式加
ELSE 'other') - 边界用左闭右开(
>=+)比 <code>BETWEEN更可控,避免重复计数 - MySQL/PostgreSQL/SQL Server 都支持,语法一致,无需适配
SELECT
CASE
WHEN age >= 0 AND age < 18 THEN 'under_18'
WHEN age >= 18 AND age < 35 THEN '18-34'
WHEN age >= 35 AND age < 60 THEN '35-59'
ELSE '60_plus'
END AS age_group,
COUNT(*) AS cnt
FROM users
GROUP BY
CASE
WHEN age >= 0 AND age < 18 THEN 'under_18'
WHEN age >= 18 AND age < 35 THEN '18-34'
WHEN age >= 35 AND age < 60 THEN '35-59'
ELSE '60_plus'
END;用 FLOOR 或 ROUND 简化等宽区间分组
当区间等宽(如每10岁一段、每100元一段),硬写 CASE WHEN 很啰嗦,用 FLOOR 或 ROUND 转换为离散标签更简洁,也方便动态调整步长。
注意:不同数据库对负数的 FLOOR 行为一致,但 ROUND 默认四舍五入,可能把 24.9 归到 20 段还是 30 段需确认;FLOOR(x / 10) * 10 更可靠。
-
FLOOR(value / 10) * 10生成区间左边界(如 23 → 20,37 → 30) - 若想显示为 "20-29" 这类字符串,用
CONCAT(FLOOR(value/10)*10, '-', FLOOR(value/10)*10 + 9) - PostgreSQL 中
FLOOR对整数字段返回 numeric 类型,GROUP BY时需保持类型一致
SELECT FLOOR(income / 10000) * 10000 AS income_floor, COUNT(*) AS cnt FROM customers GROUP BY FLOOR(income / 10000) * 10000 ORDER BY income_floor;
GROUP BY 后无法直接引用列别名,必须重复表达式
很多人写完 CASE WHEN ... END AS group_name,然后在 GROUP BY group_name 报错——这是 SQL 标准限制:GROUP BY 子句不能用 SELECT 中定义的别名,必须原样复写表达式或用序号(不推荐)。
尤其在复杂 CASE 或嵌套函数时,复制粘贴容易出错。建议先写好分组逻辑,再统一复制到 GROUP BY,不要依赖别名。
- 别名只在
SELECT和ORDER BY可用,GROUP BY、HAVING、WHERE都不行 - 某些方言(如 SQLite)支持
GROUP BY 1指代第一个 SELECT 表达式,但可读性差,跨库不安全 - 如果表达式很长,可考虑用 CTE 提前计算分组字段,再
GROUP BY别名
NULL 值和边界外数据常被忽略
区间分组最常漏掉两类数据:字段为 NULL 的记录,以及明显超出预设区间的异常值(比如年龄 -1、999)。它们不会进入任何 WHEN 分支,最终归入 ELSE——但如果你没写 ELSE,它们就直接消失了。
- 显式写
ELSE 'unknown'并检查该组数量,能快速发现脏数据 - 用
WHERE age IS NOT NULL提前过滤,但会丢失缺失值分布信息 - 对业务敏感字段(如金额、分数),建议先
SELECT MIN(), MAX()看值域,再定区间,避免“60-69”段永远为空
区间分组看着简单,真正上线时暴露的问题往往不在语法,而在原始数据的分布假设和边界处理是否真实成立。

















