不能直接用MEDIAN()函数,因Oracle和旧版MySQL不支持,且多数数据库的MEDIAN()不支持PARTITION BY;兼容性最强、逻辑最可控的方式是用ROW_NUMBER()和COUNT() OVER()手动定位中位数位置。

为什么不能直接用 MEDIAN() 函数?
多数主流数据库(如 MySQL 8.0+、PostgreSQL、SQL Server)确实支持 MEDIAN(),但 Oracle 和旧版 MySQL(<8.0)不原生支持;更关键的是,HR 系统常需按部门分组 + 排序后取中间位置,而 MEDIAN() 在部分引擎中不支持 PARTITION BY(比如早期 PostgreSQL 的聚合版 MEDIAN()),强行用反而容易出错或结果不对。
- MySQL 8.0+ 的
MEDIAN()是窗口函数,但仅限于某些发行版(如 Percona),官方版本仍无 - PostgreSQL 的
PERCENTILE_CONT(0.5)支持PARTITION BY,但必须配合ORDER BY和显式OVER() - SQL Server 2022+ 才引入
PERCENTILE_CONT窗口形式;此前只能靠ROW_NUMBER()模拟
所以实际落地时,用 ROW_NUMBER() + COUNT() OVER() 手动定位中位数位置,兼容性最强、逻辑最可控。
用 ROW_NUMBER() 和 COUNT() OVER() 计算部门中位数
核心思路:对每个部门的薪资升序排序,算出总人数 n,然后取第 FLOOR((n+1)/2) 和 CEILING((n+1)/2) 两个位置(奇数时两位置相同,偶数时取中间两数平均)。
SELECT dept_name,
AVG(salary) AS median_salary
FROM (
SELECT dept_name,
salary,
ROW_NUMBER() OVER (PARTITION BY dept_name ORDER BY salary) AS rn,
COUNT(*) OVER (PARTITION BY dept_name) AS cnt
FROM employees
) t
WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0))
GROUP BY dept_name;
-
FLOOR((cnt + 1) / 2.0)和CEIL((cnt + 1) / 2.0)保证小数参与计算(避免整数除法截断),在 MySQL/PostgreSQL/SQL Server 中都安全 -
ROW_NUMBER()必须用ORDER BY salary,不能用ORDER BY salary ASC NULLS LAST(MySQL 不支持NULLS LAST,且 HR 数据里薪资一般非空) - 如果表里有重复薪资值,
ROW_NUMBER()会强制打散顺序(加id作二级排序可稳定结果):ORDER BY salary, employee_id
遇到 NULL 或异常薪资怎么办?
HR 表中常见 salary 为 NULL、0、或明显异常值(如 9999999),直接参与中位数计算会歪曲结果。
- 先过滤掉无效值:
WHERE salary > 0 AND salary IS NOT NULL(放在子查询外层 or 内层均可,但推荐在外层,减少窗口函数计算量) - 若需保留部门结构(即使某部门全员 NULL),得用
LEFT JOIN或先SELECT DISTINCT dept_name再关联,否则该部门直接不出现在结果里 - 异常高薪(如 CEO 薪资是普通员工 100 倍)是否剔除?业务上通常要——可用
PERCENT_RANK()切掉 top 1%:WHERE PERCENT_RANK() OVER (PARTITION BY dept_name ORDER BY salary) BETWEEN 0.01 AND 0.99
性能注意点:索引和数据量
窗口函数本身不走索引,但 ORDER BY 部分能利用索引加速排序。
- 必须有复合索引:
CREATE INDEX idx_dept_salary ON employees (dept_name, salary); - 如果员工表超百万行,
COUNT() OVER(PARTITION BY ...)会触发完整扫描,此时可考虑预计算部门人数存到维表,或改用物化 CTE(PostgreSQL)/ 临时表(SQL Server)拆解 - MySQL 8.0 对大结果集的窗口函数内存消耗明显,可通过
SET sort_buffer_size = 4M调优(但别设太高,易 OOM)
窗口函数算中位数这事,看着是语法问题,实际卡点常在数据质量、索引缺失和引擎差异——写完语句先 EXPLAIN 看执行计划,比调参数更重要。

















