必须用 CASE WHEN 先将价格映射为离散标签再 GROUP BY,不可直接在 GROUP BY 中写范围表达式;推荐用 CTE 避免重复、WHERE 提前过滤提升性能,并注意 NULL 处理、边界定义及数据清洗。

用 CASE WHEN 配合 GROUP BY 实现价格区间分组
直接在 GROUP BY 里写范围表达式会报错,必须先用 CASE WHEN 把价格映射成离散的分组标签,再对这个标签做分组统计。
常见错误是试图写成 GROUP BY price BETWEEN 0 AND 99 —— 这在标准 SQL 中语法不合法,MySQL 可能容忍但逻辑不可靠。
- 推荐写法:在
SELECT和GROUP BY中都使用相同的CASE WHEN表达式 - 注意边界处理,比如
price < 100和price <= 99在浮点或 DECIMAL 类型下行为一致,但语义更清晰的是用<=显式定义上限 - 如果价格为 NULL,
CASE默认不匹配任何分支,会被归入ELSE或丢失;建议显式加上ELSE 'unknown'
SELECT
CASE
WHEN price <= 99 THEN '0-99'
WHEN price <= 199 THEN '100-199'
WHEN price <= 499 THEN '200-499'
ELSE '500+'
END AS price_range,
COUNT(*) AS count
FROM products
GROUP BY
CASE
WHEN price <= 99 THEN '0-99'
WHEN price <= 199 THEN '100-199'
WHEN price <= 499 THEN '200-499'
ELSE '500+'
END;
用 CTE 避免重复写 CASE 表达式
上面写法中 CASE 出现两次,维护成本高、易出错。PostgreSQL / SQL Server / MySQL 8.0+ 支持 CTE,可先计算分组字段再聚合。
适用场景:区间逻辑复杂、要复用分组结果做多层统计、或后续还要加过滤(如只看有库存的商品)。
- CTE 中的别名(如
price_range)不能直接用于外部GROUP BY,必须写全子查询字段或重写 - 某些旧版 MySQL(5.7 及之前)不支持 CTE,此时只能复制
CASE或改用临时表 - 性能上无本质差异,但可读性提升明显
WITH priced AS (
SELECT
id,
CASE
WHEN price <= 99 THEN '0-99'
WHEN price <= 199 THEN '100-199'
WHEN price <= 499 THEN '200-499'
ELSE '500+'
END AS price_range
FROM products
WHERE price IS NOT NULL
)
SELECT price_range, COUNT(*) AS count
FROM priced
GROUP BY price_range;
WHERE 先过滤再分组能显著提升性能
如果只关心某个类目下的价格分布,别在分组后用 HAVING 筛选,而应在 WHERE 阶段就过滤掉无关数据。
典型陷阱:把类目条件写进 HAVING 或放在 CASE 外层,导致全表扫描后再丢弃大量行。
-
WHERE category = 'electronics'应放在最外层查询或 CTE 内部 - 确保
price和常用过滤字段(如category,status)上有联合索引,例如(category, price) - 若价格字段类型是
DECIMAL(10,2),和整数比较(如price <= 199)完全安全,无需类型转换
导出区间边界为参数时要注意字符串拼接风险
业务系统中常把区间配置存在表里,动态拼 SQL 生成分组。这时候容易忽略 SQL 注入和类型隐式转换问题。
比如从配置表读出 lower_bound = 100, upper_bound = 199,然后拼成 WHEN price BETWEEN 100 AND 199 —— 看似没问题,但如果配置值来自用户输入就危险了。
- 绝对不要用字符串拼接构造
CASE分支,应通过应用层预编译参数传入(如 JDBC 的?占位符) - 如果必须动态生成 SQL,至少对数值字段做
CAST(... AS DECIMAL)显式转换,避免数据库按字符串比大小 - 区间配置表建议增加
is_active字段,避免停用区间仍被代码读取
WHERE 或 CTE 里先清洗干净,不然分组结果看着对,其实漏了关键数据。

















