WIDTH_BUCKET是Oracle/PostgreSQL的等宽分桶函数,适用于已知数值范围、需均匀分N桶并统计频次的场景,如将0–100分成绩分为4档;它返回桶编号(1~n),值<min→0,≥max→n+1,NULL值结果为NULL。

WIDTH_BUCKET 是什么,适合什么场景
WIDTH_BUCKET 是 Oracle 和 PostgreSQL(10+)支持的窗口/标量函数,用于将连续数值按等宽区间分桶,类似直方图划分。它不适用于 MySQL 或 SQL Server,这点必须先确认——如果用的是这些数据库,得换 NTILE、CASE WHEN 手动分段或应用层处理。
它适合的典型场景是:已知数据范围,想等距切分成 N 个桶并统计频次,比如“把用户年龄 0–100 均匀分成 5 档,看每档人数”。
基本语法和参数含义要盯紧边界行为
WIDTH_BUCKET(value, min_value, max_value, num_buckets) 返回整数桶编号(从 1 开始),但要注意三个关键细节:
-
min_value和max_value必须严格小于/大于实际数据极值,否则会生成额外的“溢出桶”:值 <min_value→ 返回 0;值 ≥max_value→ 返回num_buckets + 1 - 桶宽 = (
max_value - min_value) /num_buckets,结果向下取整(即左闭右开:[bucket_start, bucket_end)) - 如果
value为 NULL,结果也为 NULL,不会被计入任何桶
例如对成绩表 score 中的 marks 字段分 4 桶(0–100 分):
SELECT WIDTH_BUCKET(marks, 0, 100, 4) AS bucket_id, COUNT(*) FROM score GROUP BY WIDTH_BUCKET(marks, 0, 100, 4) ORDER BY bucket_id;
这会产生桶 0(< 0)、1(0 ≤ x < 25)、2(25 ≤ x < 50)、3(50 ≤ x < 75)、4(75 ≤ x < 100)、5(≥ 100)。通常你要过滤掉桶 0 和 5:WHERE marks BETWEEN 0 AND 99.999 或直接在 GROUP BY 外加 HAVING。
和 CASE WHEN 分桶比,有什么实际差异
用 CASE WHEN 写等宽分桶看似灵活,但容易漏边界、难复用、无法动态改桶数;而 WIDTH_BUCKET 是原子函数,计算快且语义清晰。不过它有两个硬限制:
- 不支持非数值类型(比如时间戳需先转成数值,如
EXTRACT(EPOCH FROM ts)) - 不能跳过空桶:即使某桶没人,
WIDTH_BUCKET本身不生成该行,必须配合GENERATE_SERIES(PostgreSQL)或CONNECT BY(Oracle)补全
常见错误是直接 GROUP BY 后发现只有 3 行,误以为数据不全——其实是某桶频次为 0,没被返回。补全示例(PostgreSQL):
SELECT b.bucket, COUNT(s.marks) FROM GENERATE_SERIES(1, 4) AS b(bucket) LEFT JOIN score s ON WIDTH_BUCKET(s.marks, 0, 100, 4) = b.bucket GROUP BY b.bucket ORDER BY b.bucket;
Oracle 和 PostgreSQL 的兼容性坑点
虽然函数名和主逻辑一致,但 Oracle 对 min_value/max_value 类型更敏感:若 value 是 NUMBER,那两个边界也必须是 NUMBER,不能是字符串或隐式转换的整数字面量(否则报 ORA-00932)。PostgreSQL 则宽松些,但跨类型仍建议显式 ::numeric。
另一个易忽略点:Oracle 中 WIDTH_BUCKET 在 PL/SQL 块里可用,但在某些旧版本物化视图定义中不被支持;PostgreSQL 10+ 支持,但 9.6 及以前没有——查 SELECT version(); 再决定是否 fallback。
分桶本身不难,难的是确保边界对齐业务定义,尤其是当“60 分及格”这种业务阈值不在桶边界上时,WIDTH_BUCKET 无法自动适配,这时候就得回到 CASE WHEN。

















