percent_rank() 和 ntile() 不等价于统计学分位数,因定义差异导致结果偏差;实操需依数据库选合适函数,如 postgresql 用 percentile_cont() 插值,mysql 需 row_number() 手算中位数。

用 PERCENT_RANK() 和 NTILE() 算分位数时为什么结果不对
直接套公式容易出错,因为不同数据库对分位数定义不一致——PERCENT_RANK() 返回的是「相对排名百分比」(0 到 1),而 NTILE(n) 是把行粗略均分到 n 组,两者都不等价于统计学上的分位数(如第 25 百分位数)。比如 NTILE(4) 在 5 行数据里会分出大小不等的组,最小值和最大值必然被分进第一、第四组,但它们未必对应真正的 Q1/Q3。
实操建议:
-
PERCENT_RANK()的结果需配合ORDER BY列严格单调,否则相同值会得到相同 rank,影响插值精度 -
NTILE(100)只能近似第 k 百分位,且无法处理偶数行下的线性插值(比如第 50 百分位即中位数,它可能落在两个组交界处但不返回中间值) - PostgreSQL 支持
percentile_cont(0.5),MySQL 8.0+ 需用PERCENT_RANK()+ 自连接或子查询做线性插值
在 MySQL 8.0+ 中手写中位数:绕不开 ROW_NUMBER() 和计数
MySQL 没有内置中位数聚合函数,必须靠窗口函数加逻辑判断。核心思路是:先排序编号,再根据总行数奇偶决定取中间 1 个还是 2 个平均。
示例(假设表 sales 有字段 amount):
SELECT AVG(amount) AS median
FROM (
SELECT amount,
ROW_NUMBER() OVER (ORDER BY amount) AS rn,
COUNT(*) OVER() AS cnt
FROM sales
) t
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2));
注意点:
-
ROW_NUMBER()必须用ORDER BY amount,不能用RANK()或DENSE_RANK(),否则重复值会导致编号跳空,破坏中位位置计算 -
FLOOR((cnt + 1) / 2)和CEIL((cnt + 1) / 2)统一覆盖奇偶两种情况:奇数时两值相等,偶数时取中间两个 - 如果
amount允许 NULL,需提前WHERE amount IS NOT NULL,否则ROW_NUMBER()会把 NULL 排最前或最后(取决于 SQL mode),干扰中位位置
PostgreSQL 的 percentile_cont() 和 percentile_disc() 怎么选
这两个函数语义差异明显:percentile_cont 做线性插值,返回连续分布下的理论分位值;percentile_disc 返回实际存在的、排名最接近的目标值(离散型)。
例如对 [1, 3, 5, 7] 计算中位数(0.5 分位):
-
percentile_cont(0.5) WITHIN GROUP (ORDER BY x)→4.0((3+5)/2 插值) -
percentile_disc(0.5) WITHIN GROUP (ORDER BY x)→3(第 2 小的值,即向下取整的排名)
常见误用:
- 误以为
percentile_disc(0.5)总是返回「中间那个数」——当行数为偶数时,它返回的是 floor((n+1)/2) 位置的值,不是平均 - 在需要精确统计报告(如 SLA 百分位延迟)时用了
disc,导致低估真实分位水平 -
percentile_cont要求ORDER BY列类型支持线性插值(数值、时间等),对文本列会报错
SQL Server 的 PERCENTILE_CONT() 报错 “The function 'PERCENTILE_CONT' must have an OVER clause”
这个错误不是语法写错了,而是你漏掉了 OVER 子句中的 ORDER BY——PERCENTILE_CONT 是窗口函数,即使不分区也必须显式声明排序,且不能省略 ORDER BY。
正确写法:
SELECT DISTINCT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER () AS median_salary
FROM employees;
关键细节:
-
WITHIN GROUP (ORDER BY ...)是强制的,且括号内只能有一列,不能写ORDER BY salary, dept -
OVER()不能为空,但可以不写PARTITION BY;若要按部门算中位数,得写OVER (PARTITION BY dept) - 该函数在 SQL Server 2012+ 可用,但仅支持
float输入;如果salary是int,结果仍是float,必要时需CAST
NTILE 和 PERCENT_RANK 的行为差异最容易引发线上结果偏差。










