在业务报表里,我们常常需要计算每个部门、每个地区或者每个品类下的中位数指标。和平均值不同,中位数对极端值不敏感,更能反映典型水平。借助现代数据库支持的窗口函数,可以直接在组内排序并定位中位数位置,避免繁琐的子查询和自连接。

一、什么是组内中位数
组内中位数是指在一个分组内部,将数据按大小排序后处于中间位置的数值。如果组内记录数为奇数,中位数就是正中间那条记录;如果是偶数,通常取中间两条记录的平均值。
二、使用MEDIAN窗口函数
Oracle等数据库直接提供了MEDIAN窗口函数,它可以作为聚合或分析函数使用。下面以员工表emp为例,按部门deptno计算薪资中位数:
SELECT deptno, ename, sal, MEDIAN(sal) OVER (PARTITION BY deptno) AS dept_median_sal FROM emp;
上述语句中,PARTITION BY deptno表示按部门分组,MEDIAN(sal) OVER(...)会为同一部门的每一行都返回该部门的薪资中位数。
三、使用PERCENTILE_CONT计算中位数
在标准SQL中更通用的是PERCENTILE_CONT,它属于逆分布函数。传入参数0.5即表示取第50百分位数,也就是中位数。PostgreSQL和Oracle均支持该写法:
SELECT
deptno,
ename,
sal,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY sal)
OVER (PARTITION BY deptno) AS dept_median_sal
FROM emp;
这里WITHIN GROUP (ORDER BY sal)指定了排序字段,0.5代表中位数位置。
四、MySQL中的近似处理
MySQL在8.0之前没有原生MEDIAN窗口函数,但可以利用ROW_NUMBER与计数配合实现。以下示例展示一种常见写法:
WITH ranked AS (
SELECT
deptno,
sal,
ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY sal) AS rn,
COUNT(*) OVER (PARTITION BY deptno) AS cnt
FROM emp
)
SELECT
deptno,
AVG(sal) AS dept_median_sal
FROM ranked
WHERE rn IN (FLOOR((cnt + 1)/2), CEIL((cnt + 1)/2))
GROUP BY deptno;
该查询先为组内数据编号,再取中间一个或两个位置求平均,从而得到中位数。
五、方法对比
| 数据库 | 推荐函数 | 说明 |
|---|---|---|
| Oracle | MEDIAN / PERCENTILE_CONT | 原生支持,语法简洁 |
| PostgreSQL | PERCENTILE_CONT | 遵循SQL标准 |
| MySQL | ROW_NUMBER模拟 | 低版本需手动计算 |
六、小结
使用窗口函数计算组内中位数,核心在于理解分区与排序。如果数据库支持MEDIAN或PERCENTILE_CONT,应尽量使用它们以保证性能和可读性;不支持时再用行号模拟。掌握这些写法能让你在面对分组统计需求时更加从容。