NTILE()是窗口函数,须用OVER(PARTITION BY...ORDER BY...)实现分组分位,不能与GROUP BY混用;它生成等频桶而非固定分位点,故不能替代PERCENT_RANK或CUME_DIST。
NTILE函数分组后计算分位数的正确写法
直接在
外层套
是错的——
是窗口函数,必须搭配
使用,且不能和聚合函数混用。想按某字段分组、再在每组内划分 4 个桶(即四分位),得把分组键放进
里。
不保证每桶行数完全相等,它会尽量平均分配,但余数行从第 1 桶开始顺次补入
排序依据必须显式指定:
缺失会导致结果不可控,不是报错而是随机分布
若某组只有 1 行,
仍返回
;只有 3 行则返回
,不会出现
为什么NTILE不能替代PERCENT_RANK或CUME_DIST
划的是「等频桶」(每桶大致相同行数),不是「等距分位点」。比如你有一组值
,
分出的桶是
;但如果你加一个
,就变成
——桶边界漂移了,无法对应固定的百分位数值(如 P25=30)。
需要真实分位数值(如中位数、P90)→ 用
或
(PostgreSQL/SQL Server/Oracle 支持)
只关心相对排名位置(前 25%、后 10%)→
足够,且兼容性更好(MySQL 8.0+、SQLite 3.25+、BigQuery 都支持)
可近似看作“百分位序号”,但第 1 桶不等于 P1,只是“排在最前面约 1% 的人”
常见错误:ORDER BY用了非分组字段导致逻辑错乱
在
下,如果
,但
在同一
内大量重复,
会任意打乱这些并列行的桶分配——这不是 bug,是未定义行为。实际业务中这常导致 A/B 测试分组不均或报表波动。
补救方法:加确定性次级排序,例如
(假设
唯一)
更稳妥做法:先用
打散并列,再对序号做
MySQL 用户注意:8.0 之前不支持窗口函数,强行用变量模拟
极易出错,建议升级或换思路
实战示例:按部门划分员工薪资四分位
以下语句在 PostgreSQL 和 BigQuery 中可直接运行,目标是给每个部门内员工按薪资从低到高分为 4 桶(Q1–Q4):
注意:
值为 1–4,但 Q1 不代表“第一四分位数”这个统计值,仅代表“该员工落在本部门薪资最低的那 25% 区间”。若需真正 P25 数值,应另起一行用
聚合计算。
真正难处理的,是当分组粒度极细(比如每个用户一天多条记录)、又要求严格等频时,
的整除截断逻辑会让少量组出现桶数不足的情况——这时得提前
过滤掉样本量过小的组,否则分位标签会失去可比性。
NTILE()GROUP BYNTILE()OVER()OVER(PARTITION BY ...)NTILE(4)ORDER BY value_columnNTILE(4)11,2,34NTILE()[10,20,30,40,50,60,70,80]NTILE(4)[10,20], [30,40], [50,60], [70,80]90[10,20,30], [40,50], [60,70], [80,90]PERCENTILE_CONT(0.5)PERCENTILE_DISC(0.9)NTILE()NTILE(100)PARTITION BY categoryORDER BY created_atcreated_atcategoryNTILE()ORDER BY created_at, ididROW_NUMBER() OVER(...)NTILE()NTILESELECT
dept,
name,
salary,
NTILE(4) OVER (PARTITION BY dept ORDER BY salary) AS quartile
FROM employees;quartilePERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary)NTILECOUNT(*)