要算分组后的中位数,优先用percentile_cont(0.5),因其返回插值结果(真正数学中位数),而percentile_disc(0.5)仅返回实际存在的某个值;二者均需配合over(partition by...order by...)使用,不支持纯group by语法。

PERCENTILE_CONT 和 PERCENTILE_DISC 的核心区别
直接说结论:要算分组后的中位数,优先用 PERCENTILE_CONT(0.5),不是 PERCENTILE_DISC(0.5)。前者返回插值结果(真正数学意义的中位数),后者只返回实际存在的某个值(可能偏移)。
比如分组内有 [1, 3, 5, 7] 四个数,PERCENTILE_CONT(0.5) 返回 4.0((3+5)/2),而 PERCENTILE_DISC(0.5) 返回 3(第2小的数,即下中位数)。
-
PERCENTILE_CONT要求排序列不能为NULL,否则该行被忽略;PERCENTILE_DISC同样跳过NULL - 二者都必须配合
OVER (PARTITION BY ... ORDER BY ...)使用,不支持纯聚合写法(如GROUP BY+PERCENTILE_CONT会报错) - PostgreSQL、Oracle、SQL Server(2012+)、Snowflake 支持;MySQL 目前(8.0)仍不支持这两个函数
在 GROUP BY 场景下正确调用 PERCENTILE_CONT
很多人卡在这一步:想按部门分组后直接出中位数,但写成 SELECT dept, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) FROM emp GROUP BY dept —— 这是 PostgreSQL/Oracle 的语法,不是窗口函数写法,且不适用于所有数据库。
更通用、跨平台兼容的窗口写法是:
SELECT DISTINCT dept, PERCENTILE_CONT(0.5) OVER (PARTITION BY dept ORDER BY salary) AS median_salary FROM emp;
注意:DISTINCT 是必须的,因为窗口函数会为每行返回一个值,同一分组内所有行的中位数相同,去重才得到每个部门一行结果。
- 如果业务要求保留原始行并附带中位数(比如做离差分析),就不用
DISTINCT,直接加窗口函数作为计算列 -
ORDER BY必须存在,且只能有一个排序表达式;多列排序(如ORDER BY salary, id)会导致结果不稳定,尤其当 salary 有重复时 - 某些数据库(如 BigQuery)要求
PERCENTILE_CONT的ORDER BY列类型为数值或日期,字符串会报错
MySQL 用户的替代方案:用 ROW_NUMBER 模拟中位数
MySQL 8.0 不支持 PERCENTILE_CONT,但可用 ROW_NUMBER() + COUNT(*) 手动定位中间位置。
WITH ranked AS (
SELECT
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary) AS rn,
COUNT(*) OVER (PARTITION BY dept) AS cnt
FROM emp
)
SELECT
dept,
AVG(salary) AS median_salary
FROM ranked
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2))
GROUP BY dept;
这个逻辑覆盖奇偶两种情况:奇数行取中间1个,偶数行取中间2个求均值。
- 务必用
AVG()包裹,否则偶数长度时会返回两行 -
FLOOR和CEIL在 cnt=1 时都返回 1,安全 - 性能上比原生
PERCENTILE_CONT差,数据量大时建议加(dept, salary)复合索引
容易被忽略的 NULL 和数据类型陷阱
中位数计算对 NULL 很敏感——PERCENTILE_CONT 默认跳过 NULL,但如果你的业务中 NULL 表示“未发放薪资”,而你希望它参与排序(比如排在最前),就得提前处理。
- 把 NULL 当最小值:用
ORDER BY COALESCE(salary, -999999)(注意类型匹配) - 把 NULL 当最大值:用
ORDER BY salary DESC NULLS LAST(仅 PostgreSQL/Oracle 支持NULLS LAST) - SQL Server 不支持
NULLS LAST,得用ORDER BY CASE WHEN salary IS NULL THEN 1 ELSE 0 END, salary - BigQuery 中
PERCENTILE_CONT对INT64和FLOAT64返回类型不同:输入是整型,结果仍是整型(截断),建议显式转CAST(salary AS FLOAT64)
中位数不是“开箱即用”的统计量,它的行为高度依赖数据库实现细节和你的数据分布假设。动手前先 SELECT * FROM emp WHERE dept = 'xxx' ORDER BY salary 看一眼真实排序,比查文档更快定位问题。










