直接套用PERCENT_RANK()或NTILE(2)算中位数易错:前者返回相对位置而非真实中位值,后者仅强行二分、无法稳定定位中间行;可靠方法是用ROW_NUMBER()+COUNT()取第FLOOR((n+1)/2)和CEIL((n+1)/2)行的平均值,或使用兼容性有限但语义明确的PERCENTILE_CONT(0.5)。

用 PERCENT_RANK() 和 NTILE() 算中位数容易出错
直接套用 PERCENT_RANK() 或 NTILE(2) 得到的“中间值”不等于数学定义的中位数——前者返回的是相对排序位置,后者只是强行二分分组,当行数为偶数时,NTILE(2) 可能把两个中间值分到同一组或不同组,无法稳定取到第 50% 位置的值。
真正可靠的中位数必须基于有序序列的位置计算:若总行数为 n,需取第 FLOOR((n+1)/2) 和 CEILING((n+1)/2) 行的平均值(偶数情况)或单个值(奇数情况)。
PERCENTILE_CONT() 是最简方案,但不是所有数据库都支持
PERCENTILE_CONT(0.5) 是 SQL 标准中专为连续分布设计的中位数函数,它会线性插值(比如第 3 行是 10、第 4 行是 14,则 0.5 分位返回 12),语义清晰且结果确定。但它仅在 PostgreSQL、Oracle、SQL Server(2012+)、Snowflake 等支持;MySQL 和 SQLite 完全不支持,BigQuery 用 PERCENTILE_CONT() 但语法要求 OVER() 必须含 ORDER BY,且不能和 GROUP BY 混用。
- PostgreSQL 示例:
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score) FROM students;
- SQL Server 注意:必须写成
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score) OVER (),括号不能省 - BigQuery 报错
Function PERCENTILE_CONT does not support framing clauses?说明你误加了ROWS BETWEEN ...——它不接受窗口帧,只接受空OVER()
兼容性最强的手动实现:用 ROW_NUMBER() + COUNT()
这是跨数据库(MySQL 8.0+、PostgreSQL、SQL Server、Oracle)都能跑通的写法,核心思路是给每行打序号,再和总行数比对找出中位位置:
WITH ranked AS (
SELECT score,
ROW_NUMBER() OVER (ORDER BY score) AS rn,
COUNT(*) OVER() AS cnt
FROM students
)
SELECT AVG(score) AS median
FROM ranked
WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0));
-
FLOOR((cnt + 1) / 2.0)和CEIL((cnt + 1) / 2.0)自动覆盖奇偶两种情况:cnt=5 → 得 3 和 3;cnt=6 → 得 3 和 4 - MySQL 用户注意:
CEIL()和FLOOR()对浮点数敏感,务必用2.0而非2,否则整数除法可能截断 - 如果存在大量重复值,这个方法仍正确——它按排序后物理位置取值,不是按值去重
算任意百分位数(如 90% 分位)要小心边界和插值逻辑
标准 PERCENTILE_CONT(p) 支持任意 p(0–1),但手动实现时,很多人直接套用中位数公式改成 (cnt * p),这会出错:它没考虑插值,且未处理 p=0 或 p=1 的边界。更稳妥的做法是复用 ROW_NUMBER() 序列,再用 LEAD()/LAG() 做线性插值,或退而求其次——用 NTILE(100) 分 100 组后取第 p*100 组的最小值(有偏,但快)。
- 90% 分位近似(无插值):
SELECT MIN(score) FROM (SELECT score, NTILE(100) OVER (ORDER BY score) AS tile FROM students) t WHERE tile = 90;
- 真正插值需至少两行:查出
rn = FLOOR(cnt * p)和rn = CEILING(cnt * p)对应的值,再按比例加权——这一步容易漏掉cnt * p刚好为整数的情况
手动实现百分位数时,插值逻辑和边界处理才是难点,不是排序本身。










