median()在多数数据库不可用,因sql标准未定义该函数,仅postgresql等少数支持且行为不一;跨库通用解法是row_number()+count()手动定位中间行并取均值。

为什么 MEDIAN() 在 PostgreSQL 之外基本不可用
多数数据库(如 MySQL 8.0+、SQL Server、Oracle)原生不支持 MEDIAN() 窗口函数。PostgreSQL 是个例外,但它只在 ORDER BY 子句存在时才允许作为聚合窗口函数使用,且不能直接用于 PARTITION BY 场景下的动态分组中位数计算——实际写出来会报错 ERROR: aggregate function calls cannot be nested。
真正能跨数据库落地的方案,是用 ROW_NUMBER() 配合 COUNT() 手动定位中间位置。关键在于:中位数不是“一个值”,而是“中间一个或两个值的平均”,必须区分奇偶行数。
用 ROW_NUMBER() 和 COUNT() 计算分组中位数(MySQL / SQL Server / Oracle 通用)
核心思路是:对每组数据排序后编号,再找出编号落在 (n+1)/2 和 (n+2)/2 的两行(整数除法下自动取整),最后取均值。这个表达式能同时兼容奇数和偶数行数。
以 MySQL 8.0+ 为例:
SELECT
category,
AVG(value) AS median_value
FROM (
SELECT
category,
value,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY value) AS rn,
COUNT(*) OVER (PARTITION BY category) AS cnt
FROM sales
) t
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2))
GROUP BY category;
-
FLOOR((cnt + 1) / 2)和CEIL((cnt + 1) / 2)在奇数时结果相同(如 cnt=5 → 3 和 3),偶数时为中间两个位置(如 cnt=6 → 3 和 4) - SQL Server 用户需把
FLOOR/CEIL换成ROUND((cnt + 1.0) / 2, 0, 1)和ROUND((cnt + 1.0) / 2, 0),避免整数除法截断 - Oracle 用户注意:
ROW_NUMBER()从 1 开始,但若存在重复值且未加唯一排序键(如ORDER BY value, id),可能因排序不稳定导致中位数漂移
PostgreSQL 中误用 PERCENTILE_CONT() 的典型错误
很多人看到文档里有 PERCENTILE_CONT(0.5) 就直接当窗口函数用,比如写成 PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) OVER (PARTITION BY category) ——这会报错:ERROR: window function call requires an OVER clause with a frame specification。因为 PERCENTILE_CONT 是有序集合聚合函数,不是真正的窗口函数,不能直接加 OVER。
正确写法只能是子查询或 CTE:
SELECT DISTINCT
category,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value)
FILTER (WHERE category = t.category) AS median_value
FROM sales t;
但更稳妥的是用标准写法:
SELECT category, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) AS median_value FROM sales GROUP BY category;
注意:它不支持动态窗口(如“截止到当前行”的滚动中位数),也没法嵌套在其它窗口表达式里。
性能与空值陷阱:排序字段含 NULL 时结果会错
ROW_NUMBER() 默认把 NULL 排在最前(MySQL)或最后(PostgreSQL/SQL Server,取决于 NULLS FIRST/LAST 设置),而中位数定义要求忽略 NULL 值参与计数和排序。如果字段含空,直接 ORDER BY value 会导致 COUNT(*) 和 ROW_NUMBER() 统计口径不一致。
必须显式过滤或处理:
- 在子查询中加
WHERE value IS NOT NULL - 或改用
COUNT(value)替代COUNT(*),并确保ROW_NUMBER()的ORDER BY也跳过NULL(如 PostgreSQL 加NULLS LAST) - MySQL 无
NULLS控制语法,只能靠WHERE预过滤
漏掉这点,100 行数据里有 5 个 NULL,算出来的中位数位置就偏了——不是第 50 名,而是第 47 或 52 名。











