最通用做法是用case when将价格映射为标签(如“低价”“中价”“高价”)后再group by;需注意边界等号、null处理及避免表达式导致索引失效。

用 CASE WHEN 配合 GROUP BY 实现价格分组统计
直接在 GROUP BY 里写区间表达式会报错,必须先用 CASE WHEN 把价格映射成可分组的标签(比如“低价”“中价”“高价”),再对这个标签分组。这是最通用、兼容性最好的做法,MySQL、PostgreSQL、SQL Server 都支持。
CASE WHEN price 是核心逻辑,注意边界是否包含等号,避免漏掉等于临界值的记录- 别在
SELECT和GROUP BY里重复写整个CASE表达式——可以起别名(如AS price_range),但多数数据库不支持在GROUP BY中直接引用SELECT别名,稳妥起见还是在GROUP BY里重写一遍CASE - 如果区间很多(比如按每50元一段),手写
CASE易出错,可考虑用FLOOR(price / 50)生成分组编号,再拼接描述,但要注意负数和零价商品的处理
用 WIDTH_BUCKET(Oracle/PostgreSQL)快速划分等宽区间
WIDTH_BUCKET 是专为数值分桶设计的函数,比手写 CASE 更简洁,但仅限 Oracle 和较新版本的 PostgreSQL。它能自动把数值线性划分为指定数量的等宽桶,返回桶编号(从1开始)。
- 语法是
WIDTH_BUCKET(price, 0, 1000, 5):表示把 0–1000 元区间均分为 5 桶,价格小于0进第0桶,大于1000进第6桶——这两个“越界桶”需要单独处理或过滤 - 结果只是数字编号(1~5),要转成可读名称还得套一层
CASE或VALUES查表,不能直接当分组标签用 - 如果最大最小值不确定,用子查询先算出
MIN(price)和MAX(price)再传入,但会导致查询变慢,且无法索引加速
WHERE 过滤和 NULL 值必须提前处理
价格字段为 NULL 时,CASE WHEN 所有分支都不匹配,结果为 NULL,最终会单独聚合成一行“数量=某值”,容易被误认为是有效区间。这不是 bug,是 SQL 的三值逻辑决定的。
- 加
WHERE price IS NOT NULL最干脆,适合业务上不允许空价格的场景 - 若需保留空值统计,显式加一个
WHEN price IS NULL THEN '未知'分支,并确保该分支在所有数值判断之后(否则会被前面的WHEN拦截) - 别依赖
HAVING COUNT(*) > 0来排除空分组——它只过滤聚合后结果,不影响NULL被分到哪一组
性能关键:别让分组逻辑阻止索引使用
在 WHERE 条件里对 price 做范围过滤(如 price BETWEEN 100 AND 500)通常能走索引;但一旦把 price 套进函数或表达式(比如 FLOOR(price/100) 或 CASE WHEN),就无法利用 price 字段的索引,全表扫描风险陡增。
- 优先在
WHERE子句里缩小数据集,再做分组,例如先WHERE price >= 0排除负数和 NULL,再分组 - 如果查询高频且区间固定,可建生成列(MySQL 5.7+、PostgreSQL 12+)或计算字段并为其建索引,例如
ALTER TABLE products ADD COLUMN price_level VARCHAR(10) AS (CASE WHEN price - 千万级表慎用
WIDTH_BUCKET配合子查询算动态边界,执行计划里容易出现 Materialize,内存和时间成本都高
CASE WHEN + GROUP BY 覆盖 95% 场景;真正卡性能时,才值得花精力评估生成列或物化视图。区间定义看似简单,但边界取舍、NULL 处理、索引失效这三点,上线后最容易引发数据对不上。










