MySQL 8.0.13+ 支持 PERCENTILE_CONT 和 PERCENTILE_DISC 窗口函数,需配合 OVER(PARTITION BY...ORDER BY...) 使用,不可用于 GROUP BY;PostgreSQL 支持聚合式 PERCENTILE_CONT/PERCENTILE_DISC 配合 WITHIN GROUP(ORDER BY...) 和 GROUP BY;SQLite/MySQL 5.7 等需手写排序取中位数逻辑。

MySQL 8.0+ 直接用 PERCENTILE_CONT 和 PERCENTILE_DISC
MySQL 8.0.13 起原生支持窗口函数求百分位数,PERCENTILE_CONT 返回插值结果(适合中位数),PERCENTILE_DISC 返回实际存在的值(离散型)。两者都必须配合 OVER (PARTITION BY ... ORDER BY ...) 使用,不能直接在 GROUP BY 后用。
常见错误是写成 SELECT col, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY val) FROM t GROUP BY col —— 这是 PostgreSQL 语法,MySQL 不认。
-
PERCENTILE_CONT(0.5)对每组计算连续中位数,结果可能是小数(如 [1,2,3,4] → 2.5) -
PERCENTILE_DISC(0.5)返回排序后最接近 50% 位置的原始值(如 [1,2,3,4] → 2 或 3,取决于实现) - 必须用窗口形式:
PERCENTILE_CONT(0.5) OVER (PARTITION BY group_col ORDER BY val),再配合DISTINCT或子查询去重
PostgreSQL 中用 PERCENTILE_CONT 配合 GROUP BY
PostgreSQL 支持聚合式百分位函数,可直接嵌套在 GROUP BY 查询里,语法更贴近直觉。但要注意:它不接受窗口子句,也不能和普通聚合混用(比如不能同时写 AVG(x) 和 PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x) 而不加 GROUP BY)。
典型写法是:
SELECT dept,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary,
PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY salary) AS p90_salary
FROM employees
GROUP BY dept;
这里 WITHIN GROUP (ORDER BY ...) 是强制语法,漏掉会报错 ERROR: missing WITHIN GROUP clause。
SQLite / 旧版 MySQL 没有内置函数?手写中位数逻辑
在不支持百分位函数的环境(如 SQLite、MySQL 5.7),得靠变量或自连接模拟排序位置。核心思路是:对每组数据按目标列排序,给每行打序号,再取序号落在中间区间的值。
- 中位数本质是第
FLOOR((n+1)/2)和CEILING((n+1)/2)行的平均值(偶数长度)或单一行(奇数长度) - 用
(SELECT COUNT(*) FROM t2 WHERE t2.group_col = t1.group_col AND t2.val 实现“小于等于当前值的个数”,但性能差,数据量大时明显卡顿 - MySQL 5.7 推荐用用户变量模拟 row_number:
@row := IF(@g = group_col, @row + 1, 1) AS rn,但必须确保ORDER BY group_col, val严格生效,否则序号错乱
分组后算多个百分位?别重复排序,用 CTE 或临时结果缓存
如果既要中位数又要 P10、P90,反复对同一组数据排序会浪费资源。在支持 CTE 的数据库(PostgreSQL、MySQL 8.0+、SQL Server)中,优先把分组+排序结果提前提取出来。
例如在 PostgreSQL 中:
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 employees
)
SELECT dept,
AVG(salary) FILTER (WHERE rn IN (FLOOR((cnt+1)/2.0), CEIL((cnt+1)/2.0))) AS median,
MIN(CASE WHEN rn = FLOOR(0.1 * cnt + 1) THEN salary END) AS p10
FROM ranked GROUP BY dept;
注意 FILTER 是 PostgreSQL 特有语法;MySQL 需改用 CASE WHEN + AVG 组合。关键点在于:ROW_NUMBER() 和 COUNT() OVER 只算一次,后续所有百分位都复用这个结果集。
真实场景中,百分位计算容易因空值、重复值、数据类型隐式转换出错——比如 salary 是 TEXT 类型却参与排序,结果完全错乱。动手前先 SELECT DISTINCT typeof(salary) FROM t 确认类型。










