mysql 8.0 和 sqlite 不支持 median(),oracle 的 median() 不支持 group by,故需用 row_number() 和 count() over 手写中位数逻辑:按组排序编号,取 floor((cnt+1)/2) 和 ceil((cnt+1)/2) 行求平均。

为什么不能直接用 MEDIAN()?
多数主流 SQL 引擎(如 PostgreSQL、SQL Server、BigQuery)确实提供 MEDIAN() 聚合函数,但 MySQL 8.0 和 SQLite 目前不支持,而 Oracle 的 MEDIAN() 是纯聚合函数,无法配合 GROUP BY 做分组中位数——这时窗口函数是更通用、可控的解法。
关键判断:只要你的数据库支持 ROW_NUMBER()、COUNT() OVER 和 ORDER BY 子句(MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+ 都满足),就能手写中位数逻辑,且能精确控制排序依据和空值处理。
用 ROW_NUMBER() 和 COUNT() OVER 定位中间行
核心思路是:先给每组数据按目标列排序编号,再算出该组总行数,最后取「第 N/2 行」和「第 (N/2)+1 行」(N 为偶数时取平均;奇数时取中间那行)。
实操建议:
- 必须用
ORDER BY显式指定排序依据,否则ROW_NUMBER()结果不可靠;若存在重复值,建议加一个唯一字段(如id)做二级排序,避免结果不稳定 -
COUNT(*) OVER (PARTITION BY ...)要和ROW_NUMBER()的窗口定义一致,否则行号和总数对不上 - 避免在子查询里多次写相同窗口定义,可统一用 CTE 或内联视图封装
示例(MySQL 8.0+ 计算每部门薪资中位数):
WITH ranked AS (
SELECT
dept,
salary,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary, id) AS rn,
COUNT(*) OVER (PARTITION BY dept) AS cnt
FROM employees
)
SELECT
dept,
AVG(salary) AS median_salary
FROM ranked
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2))
GROUP BY dept;
处理偶数行时的常见错误
最常踩的坑是:用 (cnt / 2) 直接取整导致下标偏移,尤其当 cnt = 4 时,4/2 = 2,但实际要取第 2 和第 3 行(不是第 2 和第 2 行)。
正确做法是统一用 FLOOR((cnt + 1) / 2) 和 CEIL((cnt + 1) / 2):
- cnt = 4 → (4+1)/2 = 2.5 → FLOOR=2, CEIL=3 ✅
- cnt = 5 → (5+1)/2 = 3 → FLOOR=3, CEIL=3 ✅(单行)
- 别用
ROUND(),它在 .5 时四舍五入规则不统一(如某些引擎向偶数舍入)
性能与兼容性注意点
窗口函数本身不索引友好,ORDER BY 列如果没有索引,大数据量下会触发文件排序,速度骤降。
实操建议:
- 确保
ORDER BY字段(如salary)上有索引,特别是配合PARTITION BY使用时 - PostgreSQL 可用
PERCENTILE_CONT(0.5)替代手写逻辑,但它是近似计算(基于插值),严格场景慎用 - SQLite 不支持窗口函数(直到 3.25.0+ 才部分支持),若需兼容,得退回到自连接或应用层计算











