正确做法是先用CASE WHEN将金额映射为离散区间标签(如'0-999'),再对该标签GROUP BY分组统计;必须确保SELECT与GROUP BY中的CASE表达式完全一致,并显式处理NULL及异常值,避免漏统。

GROUP BY 配合 CASE WHEN 实现金额分段统计
直接用 GROUP BY 对原始金额字段分组,得不到区间分布;必须先用 CASE WHEN 把数值映射成离散标签(如“0-1000”“1000-5000”),再对这个标签分组。这是最通用、兼容性最好的做法,所有主流 SQL 引擎(MySQL、PostgreSQL、SQL Server、Oracle)都支持。
常见错误是试图在 GROUP BY 里写表达式如 GROUP BY amount / 1000,这在某些数据库(如 PostgreSQL)会报错,且语义不清、边界难控。
- 分段逻辑写在
SELECT和GROUP BY里要完全一致,否则结果错乱 - 建议把
CASE WHEN提取为子查询或 CTE,避免重复书写 - 注意闭区间/开区间的定义,比如
amount 和amount 差一个边界值
SELECT
CASE
WHEN amount <h3>用 WIDTH_BUCKET(Oracle/PostgreSQL)简化等宽分桶</h3><p>如果只需要等宽区间(如每 500 一档),<code>WIDTH_BUCKET()</code> 比手写 <code>CASE WHEN</code> 更简洁,还能自动处理边界和 NULL。但它不是 SQL 标准函数,MySQL 和 SQL Server 不支持。</p><p>函数返回的是整数桶编号(从 1 开始),需配合 <code>LISTAGG</code> 或子查询还原区间描述,否则只看到数字 1、2、3……意义不明。</p>
- 参数顺序固定:
WIDTH_BUCKET(value, min_val, max_val, bucket_count) -
min_val和max_val必须覆盖全部数据,否则超出范围的值会被归入第 0 或第 n+1 桶 - 实际使用时建议先用
SELECT MIN(amount), MAX(amount)探查数据范围
SELECT CONCAT((bucket_id - 1) * 500, '-', bucket_id * 500 - 1) AS range, COUNT(*) AS cnt FROM ( SELECT WIDTH_BUCKET(amount, 0, 10000, 20) AS bucket_id FROM orders WHERE amount IS NOT NULL ) t GROUP BY bucket_id ORDER BY bucket_id;
WHERE + GROUP BY 组合过滤后再分段,避免无效计算
如果只关心某类订单(如 status = 'paid')的金额分布,别把 WHERE 放在最外层——那样会先全表分段再过滤,白白计算已取消订单的分段逻辑。
正确做法是先 WHERE 筛出目标数据集,再做分段和 GROUP BY。这对大表性能影响显著,尤其当有效数据占比很低时。
- 索引有效性依赖
WHERE字段是否被索引,amount字段本身有索引也帮不上忙 - 如果过滤条件复杂(含函数或 JOIN),考虑物化中间结果(临时表或 CTE)
- 不要在
CASE WHEN分支里加WHERE条件,语法不合法
NULL 和异常值必须显式处理,否则消失不见
GROUP BY 默认忽略 NULL 值,且 CASE WHEN 中未覆盖的值(如负数、超大异常值)也会被丢弃,导致总数对不上。线上数据常有录入错误或单位错位(比如把元存成分),这类问题在分布统计里特别刺眼。
- 务必在
CASE WHEN最后加ELSE 'other'或具体描述(如 'invalid_amount') - 单独查
SELECT COUNT(*) FROM orders WHERE amount IS NULL确认空值比例 - 用
HAVING COUNT(*) > 0可过滤掉计数为 0 的分组,但不能补回被忽略的数据
分段统计真正难的不是写法,而是定义清楚“什么是合理区间”以及“谁该进哪个桶”。业务口径变了,SQL 得跟着改,而不是硬套模板。










