不能。percent_rank() 返回相对排名百分位(0到1),但无法保证50%位置对应真实数据点,尤其偶数行时需取两值平均,而它仅能标记最接近0.5的行,不能自动计算中位数。

PERCENT_RANK 能不能直接算中位数
不能。虽然 PERCENT_RANK() 返回的是「相对排名百分位」(范围 0 到 1,首行固定为 0),但它不保证第 50% 位置一定对应一个真实数据点——尤其当行数为偶数时,真正的中位数可能落在两个值之间,而 PERCENT_RANK() 最多只能标记出最靠近 0.5 的那行(或两行),无法自动取平均。它适合筛选“前 10%”这类场景,但不适合直接替代中位数逻辑。
用 PERCENT_RANK 模拟中位数的可行做法
如果你坚持用 PERCENT_RANK()(比如已有查询框架依赖该函数、或需同时输出分位信息),可以结合 ORDER BY 和条件过滤逼近中位数:
- 先按目标列升序排序,对每行计算
PERCENT_RANK() OVER (ORDER BY value) - 找
PERCENT_RANK最接近 0.5 的行:可用ABS(PERCENT_RANK() - 0.5) = (SELECT MIN(ABS(PERCENT_RANK() - 0.5)) FROM ...),但注意 MySQL 8.0 不支持在窗口函数后直接嵌套聚合,得用 CTE 或派生表 - 更稳妥的做法是用两个子查询分别取「≤ 50% 分位的最大值」和「≥ 50% 分位的最小值」,再平均——但这已脱离
PERCENT_RANK本意,实际等价于手写ROW_NUMBER方案
示例(简化版,仅返回一个近似中位数,非严格数学中位数):
WITH ranked AS ( SELECT value, PERCENT_RANK() OVER (ORDER BY value) AS pr FROM t ) SELECT AVG(value) FROM ranked WHERE pr IN ( (SELECT MAX(pr) FROM ranked WHERE pr = 0.5) );
为什么推荐改用 ROW_NUMBER + COUNT 更可靠
MySQL 8.0 原生中位数没内置函数,但用 ROW_NUMBER() 和总行数能精确处理奇偶两种情况,且性能可控:
-
ROW_NUMBER() OVER (ORDER BY value)给每行唯一序号(从 1 开始) -
COUNT(*) OVER()获取总行数n,中位数位置就是(n+1)/2(奇数)或n/2和n/2+1(偶数) - 避免
PERCENT_RANK在重复值多时产生的并列排名“挤占”问题(例如 5 行相同值,PERCENT_RANK全是 0,无法区分)
典型写法(兼容奇偶):
WITH numbered AS ( SELECT value, ROW_NUMBER() OVER (ORDER BY value) AS rn, COUNT(*) OVER() AS cnt FROM t ) SELECT AVG(value) FROM numbered WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2));
容易被忽略的边界情况
所有基于窗口函数的中位数方案都必须显式处理空值和全 NULL 列:
-
ORDER BY value默认把NULL排在最前(MySQL 8.0),若业务要求忽略NULL,必须加WHERE value IS NOT NULL - 如果表为空,
COUNT(*) OVER()返回 0,FLOOR((0+1)/2)=0,会导致rn IN (0,1)查不到任何行,结果为NULL—— 这是正确行为,但需确认上层是否接受 - 重复值不影响
ROW_NUMBER序号分配,但会影响业务语义上的“中间位置”,这点比PERCENT_RANK更透明可预期
真正要用 PERCENT_RANK 算中位数,本质是在用错工具;它存在的意义是描述分布形状,不是定位中心点。











