percentile_cont在存储过程中性能差,因每次调用均触发独立全量排序,无法复用已排序结果;推荐用cte预排序+row_number()定位中位位置,仅排序一次。

PERCENTILE_CONT 在存储过程中不是“开箱即用”的高效解法,直接调用容易触发重复排序,性能会随数据量陡增。
为什么在存储过程里直接写 PERCENTILE_CONT(0.5) 很慢
每个 PERCENTILE_CONT 调用都会独立执行一次完整排序——哪怕你只是对同一张临时表、同一列反复计算多个分组的中位数。SQL Server 和 PostgreSQL 都不复用已排序结果,导致 N 次调用 = N 次全量排序。
- 典型场景:存储过程里对 10 个部门分别算薪资中位数,写了 10 个
PERCENTILE_CONT窗口表达式 → 内部排序执行 10 次 - 更隐蔽的问题:如果先
ORDER BY salary做了结果集排序,再调用PERCENTILE_CONT,优化器通常也不会复用这个排序,仍会重新排 - 执行计划里常看到多个
Sort算子并列,就是信号
SQL Server 存储过程中推荐的替代方案
用 CTE 预排序 + 行号定位,把排序成本压到一次,后续逻辑只查行号范围。兼容 SQL Server 2005+,不依赖 PERCENTILE_CONT。
示例(输入为表变量 @input):
WITH ranked AS (
SELECT value,
ROW_NUMBER() OVER (ORDER BY value) AS rn,
COUNT(*) OVER() AS cnt
FROM @input
WHERE value IS NOT NULL
)
SELECT AVG(0.0 + value) AS median
FROM ranked
WHERE rn IN ((cnt + 1) / 2, (cnt + 2) / 2);
-
(cnt + 1) / 2和(cnt + 2) / 2在整数除法下自动覆盖奇偶:奇数时两值相等,偶数时取中间两个位置 -
AVG(0.0 + value)强制转浮点,避免整数除法截断(如INT列返回3而非3.5) - 若需按分组计算(如每部门一个中位数),把
COUNT(*) OVER()改成COUNT(*) OVER (PARTITION BY dept),ROW_NUMBER()同理加PARTITION BY dept
PostgreSQL 存储过程里慎用聚合式 PERCENTILE_CONT
PostgreSQL 允许 PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) 这种聚合写法,看起来简洁,但在存储过程里容易踩两个坑:
- 如果
value列含NULL,整个结果返回NULL(不是跳过,是整条聚合结果失效);必须显式过滤或用COALESCE - 在
PL/pgSQL函数中多次调用该聚合式写法,仍无法共享排序——每次WITHIN GROUP都是独立子查询级排序 - 想真正复用排序,得用
CREATE TEMP TABLE AS SELECT ... ORDER BY物化排序结果,再从临时表上查行号逻辑,和 SQL Server 思路一致
Oracle 和 MySQL 8.0+ 用户要注意的隐性成本
Oracle 的 PERCENTILE_CONT 默认走采样估算(尤其未加 RESPECT NULLS 或 ORDER BY 不稳定时),实际中位数可能偏差 1–2%;MySQL 8.0.13+ 虽支持,但只接受窗口形式(OVER()),且不支持聚合式 WITHIN GROUP,必须用 DISTINCT 或外层去重,容易多出无效行。
所有数据库里最容易被忽略的一点:中位数计算本身不关心业务语义——它只忠于非空、已排序的数据子集。如果你的存储过程要处理“NULL 代表未入职”,却没提前转换,最后算出的就只是“在职人员中位数”,而不是你文档里写的“全员薪资中位数”。










