sql无标准median()函数,postgresql 14+用percentile_cont(0.5) within group (order by col)计算中位数,mysql 8.0+/sql server需用row_number()+count()手动模拟,均须处理null、排序稳定性及数据类型。

SQL里没有MEDIAN()函数,所谓“非正态分布数据的中位数”只是强调它比均值更鲁棒——但计算逻辑完全一样:排序后取中间位置。关键不是分布形态,而是你用的数据库支不支持、怎么写才不出错。
PostgreSQL 14+:用percentile_cont(0.5)最省心
这是目前最接近“标准中位数”的写法,自动忽略NULL,返回插值结果(偶数行时取两中间值平均),语义清晰:
SELECT category, percentile_cont(0.5) WITHIN GROUP (ORDER BY value) AS median_value FROM sales WHERE value IS NOT NULL GROUP BY category;
-
WITHIN GROUP (ORDER BY ...)不可省略,漏掉会报错 - 如果原始字段是
INTEGER,结果可能是DOUBLE PRECISION(如4.5),业务上是否接受需确认 - 不加
WHERE value IS NOT NULL时,percentile_cont虽会跳过NULL,但ORDER BY行为受NULLS FIRST/LAST影响,默认因版本而异
MySQL 8.0+ / SQL Server:靠ROW_NUMBER() + COUNT()手动定位
必须显式处理奇偶、空值和排序稳定性,否则在重复值多或单行分组时结果错位:
- 先用
ROW_NUMBER() OVER (PARTITION BY category ORDER BY value, id)——id是为避免相同value导致序号不稳定 - 用
COUNT(*) OVER (PARTITION BY category)算每组总行数cnt - 中位位置统一为
FLOOR((cnt + 1) / 2)和CEIL((cnt + 1) / 2),再用AVG()包一层 - 务必在外层
WHERE value IS NOT NULL过滤,否则ROW_NUMBER()仍会给NULL编号,拉偏位置
为什么不能用AVG()或PERCENTILE_DISC()凑合?
AVG()是均值,对异常值敏感,和中位数统计意义完全不同;PERCENTILE_DISC(0.5)虽返回实际存在的值(不插值),但:
- PostgreSQL中它和
percentile_cont一样需要WITHIN GROUP,不支持窗口语法 - SQL Server中
PERCENTILE_DISC必须配OVER(),但只能返回每组一个值,无法直接用于每行带中位数的场景 - 两者都强制全列排序,10万行以上性能明显劣于
AVG(),且无法利用索引
真正容易被忽略的是:中位数计算本身不关心“是否正态”,只依赖有序序列;但如果你的数据里有大量NULL或业务上要求严格整数结果,就得在ORDER BY和最终AVG()前做显式清洗和类型转换——这点比函数选型更影响结果可信度。










