mysql 8.0.13+ 支持 percentile_cont(线性插值,如[1,2,3,4]→2.5)和 percentile_disc(取实际值,如[1,2,3,4]→2),二者均需 within group (order by ...) 且不可用于 group by 后的普通聚合,必须配合 over(partition by ... order by ...) 窗口语法,排序字段需预先过滤 null。

MySQL 8.0+ 直接用 PERCENTILE_CONT 和 PERCENTILE_DISC
如果你用的是 MySQL 8.0.13 及以上,不用手写窗口函数嵌套,直接调标准聚合函数就行。但注意:PERCENTILE_CONT 返回插值结果(连续型中位数),PERCENTILE_DISC 返回实际存在的值(离散型中位数)。两者在偶数行时表现不同:
-
PERCENTILE_CONT(0.5)对 [1,2,3,4] 返回 2.5;PERCENTILE_DISC(0.5)返回 2(向下取实际存在的第 2 小值) - 必须配合
OVER (PARTITION BY ... ORDER BY ...),不能放在普通GROUP BY后——否则报错Window function is not allowed in GROUP BY - 排序字段不能为
NULL,否则该组结果全为NULL;建议提前用WHERE col IS NOT NULL过滤
SELECT dept, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_cont, PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY salary) AS median_disc, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary) AS q1 FROM employees WHERE salary IS NOT NULL GROUP BY dept;
PostgreSQL 用 percentile_cont() 和 percentile_disc()
语法和 MySQL 类似,但 PostgreSQL 支持在普通聚合中使用(即允许 GROUP BY + 聚合函数),更灵活。不过要注意:它不接受窗口子句,而是把 ORDER BY 写在函数参数里:
-
percentile_cont(0.5) WITHIN GROUP (ORDER BY salary)是合法的,OVER ()反而会报错 - 如果分组内只有 1 行,两个函数都返回那唯一值;但若全为
NULL,结果也是NULL,不会报错 - 计算 90 分位数时别写
0.9拼错成90——这是新手最常触发的numeric field overflow
SELECT team, percentile_cont(0.5) WITHIN GROUP (ORDER BY score) AS median, percentile_cont(0.9) WITHIN GROUP (ORDER BY score) AS p90 FROM results GROUP BY team;
SQLite / 旧版 MySQL 需手写窗口逻辑
没有内置分位函数时,核心思路是:先按组排序并编号,再根据总行数算出目标位置(如中位数位置 = (n+1)/2),最后用条件聚合捞出对应行。难点在于处理奇偶、空值、重复值:
- 用
ROW_NUMBER() OVER (PARTITION BY grp ORDER BY val)编号,别用RANK()——重复值会导致编号跳空,中位数位置偏移 - 计算每组总行数要用
COUNT(*) OVER (PARTITION BY grp),不能在外部GROUP BY后用COUNT(*),否则窗口丢失 - 中位数需兼容奇偶:目标位置设为
FLOOR((cnt+1)/2.0)和CEIL((cnt+1)/2.0),然后取平均(偶数)或单值(奇数)
SELECT
grp,
AVG(val) AS median
FROM (
SELECT
grp, val,
ROW_NUMBER() OVER (PARTITION BY grp ORDER BY val) AS rn,
COUNT(*) OVER (PARTITION BY grp) AS cnt
FROM data
WHERE val IS NOT NULL
) t
WHERE rn IN (FLOOR((cnt+1)/2.0), CEIL((cnt+1)/2.0))
GROUP BY grp;
分位数精度与性能陷阱
高基数分组(比如百万级用户按城市分组)下,PERCENTILE_CONT 可能比手写 ROW_NUMBER 慢 3–5 倍——因为前者强制全局排序后插值,后者可利用索引加速局部排序。
- 确保排序字段有索引,尤其是
PARTITION BY a ORDER BY b场景,联合索引(a,b)能显著提速 - 分位数不是统计学意义上的“估计”,而是确定性计算;若数据分布倾斜严重(如 99% 值集中在 0–10,1% 在 1000+),中位数仍只反映中间位置,不代表典型值
- 导出报表时别直接用
PERCENTILE_CONT(0.5)当“平均表现”宣传——业务方容易误解为均值,实际二者差异可能极大
真正麻烦的从来不是怎么写出来,而是确认你用的那个“中位数”到底符合当前业务场景对“中间值”的定义:要插值?要真实样本?要不要排除异常值?这些得在写 SQL 前就和产品对齐清楚。










