percentile_cont 是基于连续分布模型的线性插值百分位函数,设为0.5时按数学定义计算中位数:偶数行取中间两值平均,奇数行取正中原始值;需配合 within group(order by ...) 或 over(partition by ... order by ...) 使用,非聚合函数,不支持 mysql/sqlite。

PERCENTILE_CONT 是什么,为什么它能算中位数
PERCENTILE_CONT 是 SQL 标准中定义的连续分布百分位函数,不是简单取排序后中间位置的值,而是基于线性插值计算——这正好符合中位数在偶数行时“取中间两数平均值”的数学定义。它必须配合 OVER 子句使用,且要求指定排序(ORDER BY),否则会报错:Window function PERCENTILE_CONT requires an ORDER BY clause。
注意:它只在 PostgreSQL、SQL Server、Oracle、Snowflake 和 BigQuery(启用标准 SQL)中原生支持;MySQL 和 SQLite 不支持,硬用会直接报错 function PERCENTILE_CONT does not exist。
常见误用是把它当聚合函数写在 SELECT 里却不加窗口定义,比如:SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score) —— 这其实是 PERCENTILE_CONT 的**聚合形式**(PostgreSQL/SQL Server 支持),和窗口形式语法不同、行为也不同,别混用。
按分组计算中位数的正确写法
核心是把 PERCENTILE_CONT(0.5) 放进窗口函数,并在 OVER 中同时指定分组(PARTITION BY)和排序(ORDER BY)。不能只写 ORDER BY 而漏掉 PARTITION BY,否则算的是全表中位数,不是每组一个。
示例:按部门计算员工薪资中位数
SELECT
dept,
salary,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY dept ORDER BY salary) AS median_salary
FROM employees;
但这样会为每一行都重复输出相同中位数。如果只要每组一行结果,得配合去重或子查询:
- 用
DISTINCT ON(PostgreSQL):SELECT DISTINCT ON (dept) dept, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) OVER (PARTITION BY dept) AS median FROM employees - 用
ROW_NUMBER()配合过滤(通用):先算出每组中位数值,再用外层GROUP BY+MAX()提取(因为窗口结果在组内恒定,MAX()只是安全提取)
PERCENTILE_CONT(0.5) 和 PERCENTILE_DISC(0.5) 的区别
两者都算中位数,但策略不同:PERCENTILE_CONT(0.5) 插值,PERCENTILE_DISC(0.5) 直接取排序后位置最接近 N×0.5 的那个实际值(向下取整,即第 ⌊(N+1)/2⌋ 个)。
例如数据 [10, 20, 30, 40](N=4):
-
PERCENTILE_CONT(0.5)→ 插值得到(20 + 30) / 2 = 25 -
PERCENTILE_DISC(0.5)→ 取第 ⌊(4+1)/2⌋ = 2 个值,即20
业务上若明确要求“取实际存在的中间值”,用 PERCENTILE_DISC;若接受插值结果(更符合统计学中位数定义),选 PERCENTILE_CONT。别只看名字里的 “CONT”,以为它一定“更准”——它只是定义不同。
性能和 NULL 值处理要注意什么
PERCENTILE_CONT 默认忽略 NULL 值参与排序和计算,这点和 ORDER BY 一致。但如果字段本身大量为 NULL,可能导致某组有效数据过少,中位数结果失真,建议提前过滤或补缺。
性能方面:该函数需对每个分组内部完整排序,数据量大时 I/O 和内存开销明显。在千万级表上跑分组中位数,可能比等价的 ROW_NUMBER() + 条件过滤慢 2–3 倍。如果只是临时查,没问题;若高频调用,考虑物化中位数结果到汇总表。
另外,PostgreSQL 对 PERCENTILE_CONT 窗口函数要求排序字段类型必须支持 ORDER BY(比如不能是 jsonb),而聚合形式反而更宽松——这个细节很容易在迁移 SQL 时踩坑。










