NTILE() 实现可控等频分桶,按排序位置切分:前余数个桶多1行,其余少1行;不支持MySQL 5.7及更早版和SQLite;需用CTE固化结果、ORDER BY加唯一列保稳定,且不适用于数值区间分桶。

NTILE() 适合分桶分析,不是因为它“绝对平均”,而是它能按排序位置做可控的等频切分——前提是你要清楚它怎么分配余数行。
NTILE(n) 的分桶逻辑是确定的,但不等于“每桶行数相同”
它把当前窗口内的所有行,按 ORDER BY 排序后从头到尾编号,再按顺序“切片”。分配规则只有一条:前 N % bucket_count 个桶各多 1 行,其余桶少 1 行。比如 103 行分 10 桶:103 / 10 = 10 余 3,所以第 1–3 桶各有 11 行,4–10 桶各 10 行。
- 这个行为在 PostgreSQL、SQL Server、Oracle、BigQuery、Snowflake 中完全一致
- MySQL 8.0+ 才支持;5.7 或更早版本会报错
FUNCTION xxx.NTILE does not exist - SQLite 完全不支持,得用
(ROW_NUMBER() OVER (ORDER BY x) - 1) / bucket_size + 1手写模拟
为什么不能用 WHERE 直接过滤 NTILE 别名
因为 NTILE() 是窗口函数,计算发生在整个查询逻辑的早期阶段——它看到的是未被 WHERE 过滤前的完整结果集。一旦你在外层加了 WHERE bucket = 1,实际执行顺序是:先算出所有行的桶号 → 再按条件过滤 → 最终桶 1 的行数可能远少于预期(尤其当原始数据有大量 NULL 或低值被过滤掉时)。
- 正确做法是用 CTE 或子查询固化桶号:
WITH ranked AS (SELECT *, NTILE(4) OVER (ORDER BY sales DESC) AS quartile FROM t) SELECT * FROM ranked WHERE quartile = 1 - 如果 ORDER BY 字段含 NULL,默认排序行为因数据库而异(PostgreSQL 可用
NULLS LAST,SQL Server 得靠ISNULL(col, -999999)干预)
ORDER BY 不稳定会导致 NTILE 结果不可复现
当排序字段存在重复值(比如多人销售金额相同),且没加唯一键兜底,数据库可能每次执行都以不同顺序排列这些“并列行”,导致同一行在不同查询中落入不同桶——这不是 bug,是 SQL 标准允许的“不确定函数”行为。
- 解决方法:在
ORDER BY末尾追加一个唯一列,例如ORDER BY sales DESC, id ASC - 避免用表达式排序,如
ORDER BY UPPER(name),会跳过索引,千万级表上易触发磁盘排序甚至 OOM - 确认
ORDER BY字段有索引,特别是带PARTITION BY的场景(如PARTITION BY dept_id ORDER BY salary)
真正容易被忽略的点是:NTILE 分的是“排序位置”,不是“数值区间”。想按销售额 0–100 万、100–200 万分桶?该用 CASE WHEN 或 WIDTH_BUCKET()(PostgreSQL/Oracle),而不是 NTILE。

















