NTILE函数将窗口内行按ORDER BY排序后均分为N个连续编号桶(从1开始),余数行优先分给前桶;必须配合ORDER BY,支持PARTITION BY分组独立分桶,不等宽、不按值区间,而是严格按行序切分。

NTILE函数的基本用法和分桶逻辑
NTILE 不是四舍五入分组,也不是按值范围切片,而是把**当前窗口内的行数均分为 N 个编号连续的桶**,编号从 1 开始。如果总行数不能被 N 整除,前面的桶会多分配 1 行——这是它和 WIDTH_BUCKET(按值区间)或 ROW_NUMBER 的本质区别。
常见错误是以为 NTILE(4) 会严格按数值大小“等宽分段”,结果发现同一数值可能落在不同桶里,或者桶内行数不一致——这恰恰说明你没理解它是按**行序**而非**值序**切分的。
必须搭配 ORDER BY 子句,否则报错:Window function ntile requires an ORDER BY clause。排序依据决定了分桶顺序,不是分桶依据本身。
在 GROUP BY 后对每组独立分桶的写法
想对每个分组(比如每个 category)各自执行 NTILE,不能只靠 GROUP BY,必须用窗口函数 + PARTITION BY。
错误写法:SELECT category, NTILE(3) OVER () FROM sales GROUP BY category —— 这会把全表当一个窗口,且 GROUP BY 和窗口函数混用易出错。
正确结构是:
SELECT category, product, amount, NTILE(3) OVER (PARTITION BY category ORDER BY amount DESC) AS bucket FROM sales;
-
PARTITION BY category确保每组单独计数、单独分桶 -
ORDER BY amount DESC决定组内行的排列顺序,影响谁进桶 1/2/3 - 若某组只有 2 行却指定
NTILE(3),结果是桶号为 1 和 2(不会出现 3),因为最多只能分出 min(N, 行数) 个非空桶
NTILE(2) 和中位数计算的关系
很多人用 NTILE(2) 辅助算中位数,但要注意:它只给出“前半部分”和“后半部分”的标记,不直接返回中位数值。
例如 5 行数据,NTILE(2) 分配为 [1,1,1,2,2];7 行则是 [1,1,1,1,2,2,2]。中间位置是否落在桶 1 或桶 2,取决于行数奇偶性。
真正取中位数时,更稳妥的是组合 ROW_NUMBER() 和 COUNT(*),或者用 PERCENT_RANK() 判断是否接近 0.5。依赖 NTILE(2) 取“桶 1 最大值”或“桶 2 最小值”容易在偶数行时漏掉边界情况。
性能与兼容性注意事项
NTILE 是标准 SQL:2003 特性,在 PostgreSQL、SQL Server、Oracle、Snowflake 中支持良好;MySQL 8.0+ 才支持,MariaDB 目前不支持。
大数据量下性能取决于 ORDER BY 字段是否有索引。如果 ORDER BY 是表达式(如 ORDER BY UPPER(name))或涉及多列且无复合索引,窗口排序开销会明显上升。
别在子查询里嵌套多层 NTILE,比如先分 10 桶再对每桶重分 5 桶——这会触发多次全量排序,实际执行计划往往比预期慢几倍。需要多级分桶时,优先考虑用 CASE WHEN + PERCENT_RANK() 或业务逻辑前置计算。
分桶数量 N 建议控制在 100 以内。N 超过行数时,每个桶最多 1 行,但生成大量稀疏编号反而降低可读性;N=1 则所有行都是 1,失去分桶意义。











