mysql 8.0.13+ 直接用 percentile_cont(0.5) within group (order by salary) over (partition by department) 计算中位数最省事,需配合 over() 使用且自动跳过 null;postgresql 同样支持但须用 group by 和 within group;sqlite 和 mysql 5.7 需手写行号模拟;中位数适用于偏态分布,avg 更适合对称分布和总量分析。

MySQL 8.0+ 直接用 PERCENTILE_CONT 最省事
如果你用的是 MySQL 8.0.13 及以上版本,PERCENTILE_CONT(0.5) 就是专为中位数设计的窗口函数,它自动处理奇偶行数、空值过滤和排序逻辑。比手写子查询稳定得多,也比 AVG() 更抗异常值干扰。
常见错误是直接套在 GROUP BY 外层:它必须配合 OVER() 使用,且不能和聚合函数混用。正确写法是:
SELECT DISTINCT
department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY department) AS median_salary
FROM employees;
注意:PERCENTILE_CONT 默认返回 DOUBLE 类型,如果字段是 DECIMAL,可能需要显式 CAST;另外它会跳过 NULL 值,这点和 AVG() 一致,但和某些手写方案不同。
PostgreSQL 用 PERCENTILE_CONT 或 percentile_disc 要分场景
PostgreSQL 同样支持 PERCENTILE_CONT(0.5)(线性插值,偶数时取中间两数平均),以及 percentile_disc(0.5)(离散取值,偶数时取下中位数)。选哪个取决于业务定义——比如薪资分析通常用 PERCENTILE_CONT,而等级评定可能要求严格取已有值,就得用 percentile_disc。
关键限制:这两个函数只能用于聚合上下文,不能直接出现在 SELECT 列表里不带 GROUP BY。如果要按部门算中位数,必须写成:
SELECT department, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary FROM employees GROUP BY department;
别漏掉 WITHIN GROUP 子句,否则报错 ERROR: window function call requires an OVER clause —— 这是新手最常卡住的地方。
SQLite 和老版本 MySQL 只能手写排序+行号模拟中位数
SQLite 没有原生中位数函数,MySQL 5.7 及更早版本也不支持 PERCENTILE_CONT。这时得靠变量或子查询生成行号,再根据总行数奇偶性取值。核心难点不是逻辑,而是性能和 NULL 处理。
以 MySQL 5.7 为例,典型写法是:
SELECT AVG(salary) AS median_salary
FROM (
SELECT salary,
@row:=@row + 1 AS row_num,
@cnt:=@cnt + IF(salary IS NOT NULL, 1, 0) AS cnt
FROM employees
CROSS JOIN (SELECT @row:=0, @cnt:=0) r
WHERE salary IS NOT NULL
ORDER BY salary
) t
WHERE t.row_num IN (FLOOR((@cnt + 1) / 2), CEIL((@cnt + 1) / 2));
这里容易踩的坑有三个:
• 必须先 WHERE salary IS NOT NULL,否则 @cnt 计数不准;
• @row 和 @cnt 必须用 CROSS JOIN 初始化,不能只在 SELECT 里赋值;
• 奇数时两个 IN 条件其实指向同一行,AVG 不影响结果,但写法必须统一。
为什么不用 AVG() 替代中位数?看数据分布再决定
中位数的价值不是“更高级”,而是对偏态分布敏感度低。比如一个团队 9 人月薪 1 万,1 人 CEO 拿 100 万,AVG() 是 19 万,严重高估普通员工水平;而中位数仍是 1 万,反映真实集中趋势。
但要注意:如果数据本身分布对称(如正态)、且你关心的是总量均衡(例如预算分配),AVG() 反而更合适。盲目替换中位数可能导致误读——特别是当业务指标明确定义为“人均”而非“典型值”时。
真正该警惕的不是平均值本身,而是没看直方图就直接用 AVG()。执行 SELECT MIN(x), MAX(x), STDDEV(x), COUNT(*) FROM t 花不了两秒,却能避免绝大多数偏差误判。











