percentile_cont 是窗口函数,必须配合 over() 使用,且需在 within group(order by ...) 中指定排序,否则报错;它不支持直接 group by,计算分组中位数须用 partition by。

PERCENTILE_CONT 为什么必须配合 OVER() 使用
PERCENTILE_CONT 是窗口函数,不是聚合函数,不能直接写成 SELECT PERCENTILE_CONT(0.5) FROM t GROUP BY x —— 这会报错 ERROR: window function calls require an OVER clause。它必须搭配 OVER() 子句,且内部需指定排序(ORDER BY),否则语法不合法。
常见误用是想“按组算中位数”,却漏掉 PARTITION BY,结果算出的是全表分位数而非分组分位数。
- 正确结构:
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) OVER (PARTITION BY group_col) - PostgreSQL 从 9.4 起支持该语法;MySQL 不支持
PERCENTILE_CONT,需用变量或ROW_NUMBER()模拟 - SQL Server 2012+ 支持,但语法略有不同:
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) OVER (PARTITION BY group_col)—— 注意没有OVER里的ORDER BY,只允许PARTITION BY
分组中位数的两种写法:窗口 vs 子查询
如果只要每个分组一个中位数值(比如每部门工资中位数),用窗口函数后去重更高效;但如果还要关联原始行字段(如员工姓名+所在部门中位工资),就得用子查询或 CTE 避免重复计算。
示例(PostgreSQL):
SELECT DISTINCT
dept,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY dept) AS median_salary
FROM employees;
但若要查出“工资 ≥ 所在部门中位数的所有员工”,就得先算中位数再 JOIN:
WITH dept_med AS (
SELECT dept,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS med
FROM employees
GROUP BY dept
)
SELECT e.name, e.salary, d.med
FROM employees e
JOIN dept_med d ON e.dept = d.dept
WHERE e.salary >= d.med;
PERCENTILE_CONT(0.5) 和 PERCENTILE_DISC(0.5) 的关键区别
两者都算中位数,但插值逻辑不同:PERCENTILE_CONT 线性插值(返回浮点值,可能不在原始数据中),PERCENTILE_DISC 取实际存在的值(返回离散值,一定是某行的原始值)。
- 数据为
[100, 200, 300, 400]时:PERCENTILE_CONT(0.5)返回250.0(200 和 300 中间),PERCENTILE_DISC(0.5)返回200(第 2 个值) - 当业务要求“必须是真实出现过的工资数”(如合规报告),优先选
PERCENTILE_DISC -
PERCENTILE_CONT对空值敏感:默认忽略NULL,但若整组全是NULL,结果也是NULL;建议提前用WHERE value IS NOT NULL过滤
性能隐患:大数据量下 ORDER BY 在 OVER 中的开销
PERCENTILE_CONT 内部依赖排序,而 OVER (PARTITION BY x ORDER BY y) 会为每个分组单独排序。当分组多、单组数据大时(例如百万级用户按城市分组算收入 90 分位),排序成本陡增,可能比用 ROW_NUMBER() + 自连接还慢。
优化方向:
- 确保
ORDER BY字段有索引,尤其是复合索引(group_col, value) - 避免在
SELECT中对同一字段反复调用多个分位(如同时算 0.25/0.5/0.75)——每个都要独立排序;可改用PERCENTILE_CONT数组形式(PostgreSQL 14+ 支持PERCENTILE_CONT(ARRAY[0.25,0.5,0.75]) WITHIN GROUP...) - 如果只是近似分位,考虑用
APPROX_PERCENTILE(Trino/Presto)或采样估算,避开全量排序
真正卡住的往往不是语法,而是没意识到它背后藏着一次隐式排序 —— 尤其在嵌套 CTE 或视图里反复调用时,执行计划容易失控。










