不能直接用median()函数,因为sql无标准median(),mysql 8.0+和sql server不支持,postgresql等虽有但仅为聚合函数、非窗口函数,无法分组动态计算;必须用row_number()+count()手动定位或percentile_cont(0.5)替代。

为什么不能直接用 MEDIAN() 函数?
PostgreSQL 和 Oracle 确实有 MEDIAN() 聚合函数,但 MySQL(直到 8.0.33)和 SQL Server 完全不支持它;SQLite 则压根没有。更关键的是,即使支持,MEDIAN() 是纯聚合函数,无法按分组动态计算(比如“每个部门的薪资中位数”),也不支持与过滤、排序逻辑联动。所以实际项目里,90% 的中位数需求得靠窗口函数 + 子查询手动构造。
真正能跨数据库、可控性强、还能嵌入复杂条件的方案,只有基于 ROW_NUMBER() 或 PERCENT_RANK() 的手动定位法。
用 ROW_NUMBER() 定位中间行(偶数取平均)
核心思路:给有序数据编号,找出第 FLOOR((n+1)/2) 和 CEILING((n+1)/2) 行(即中间一或两行),再取平均。
以 MySQL 8.0+ 或 PostgreSQL 为例:
SELECT
dept,
AVG(salary) AS median_salary
FROM (
SELECT
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary) AS rn,
COUNT(*) OVER (PARTITION BY dept) AS cnt
FROM employees
) t
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2))
GROUP BY dept;
-
ROW_NUMBER()必须配合PARTITION BY dept ORDER BY salary,否则全局编号没意义 -
COUNT(*) OVER (PARTITION BY dept)不能写成子查询里的(SELECT COUNT(*) FROM employees e2 WHERE e2.dept = e1.dept)—— 那样会显著拖慢性能,尤其数据量大时 - 注意
FLOOR和CEIL在奇偶场景下结果一致(如 cnt=5 → (5+1)/2=3 → FLOOR=CEIL=3),所以IN写法天然兼容单双数
用 PERCENT_RANK() 近似替代(适合大数据量)
当表有千万级记录且不要求严格精确时,PERCENT_RANK() 可避免两次扫描,性能更好:
SELECT DISTINCT
dept,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY dept) AS median_salary
FROM employees;
⚠️ 但注意:PERCENTILE_CONT() 不是所有数据库都支持(MySQL 8.0.33+ 才加,SQL Server 用 PERCENTILE_CONT,而 SQLite 仍不支持)。而且它返回的是插值结果(比如 [100,200] 中位是 150),不是原始数据中的某一行值——如果你的业务要求“必须是真实存在的薪资”,就不能用这个。
- PostgreSQL 和 SQL Server 支持
PERCENTILE_CONT(0.5),语义明确,推荐在支持环境下优先使用 - MySQL 在 8.0.33 前只能靠
ROW_NUMBER()方案;8.0.33+ 虽然加了PERCENTILE_CONT,但文档注明“可能返回非原始值”,需确认业务是否接受 - 别误用
PERCENT_RANK()本身做判断条件(如WHERE PERCENT_RANK() BETWEEN 0.49 AND 0.51)——它不保证唯一性,容易漏行或多行
子查询嵌套时最容易错的三处
把窗口函数塞进子查询后,新手常掉坑里,不是结果为空,就是报错 This function requires an OVER clause。
- 窗口函数(如
ROW_NUMBER()、COUNT() OVER)**不能出现在 WHERE 或 GROUP BY 中**——必须先在子查询里算好,再在外层过滤或聚合 - 如果外层要
GROUP BY dept,子查询里PARTITION BY dept是必须的,但别重复写GROUP BY,否则 MySQL 会报错(ONLY_FULL_GROUP_BY 模式下) - 别在子查询里对窗口函数结果做
ORDER BY,比如ORDER BY rn—— 这会让优化器误以为你要排序整个中间结果集,极大影响性能;排序应在最外层需要时才加
中位数看着简单,但跨数据库兼容性、空值处理(salary IS NOT NULL 得提前过滤)、以及分组键缺失时的默认行为,都是上线前必须验证的点。尤其是 NULL 值——ORDER BY salary 默认把 NULL 排最前还是最后,不同数据库不同,不显式写 ORDER BY salary ASC NULLS LAST 就可能算错。










