MEDIAN()不是标准SQL函数,仅Oracle支持其聚合用法(如MEDIAN(salary) GROUP BY deptno),但不支持OVER窗口语法;PostgreSQL推荐PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY value)配合GROUP BY;MySQL和SQL Server需用ROW_NUMBER()+COUNT()手动定位中间行计算。

MEDIAN() 不是标准 SQL 函数,绝大多数数据库(PostgreSQL、MySQL、SQL Server)都不支持 MEDIAN() 窗口或聚合用法——直接写会报错,比如 ERROR: function median() does not exist 或 Invalid column name 'median'。
Oracle 里能用 MEDIAN(),但仅限聚合,不能加 OVER
Oracle 是个例外:它原生支持 MEDIAN() 聚合函数,语法是 MEDIAN(salary),配合 GROUP BY deptno 可直接出每组中位数。但它不接受 OVER (PARTITION BY ...),所以无法在每行返回同组中位数(即“窗口式中位数”)。若硬要实现,得套子查询或 JOIN 回原表。另外注意:MEDIAN() 自动忽略 NULL,无需额外过滤。
PostgreSQL 推荐用 PERCENTILE_CONT(0.5) + GROUP BY
这是最接近语义、且结果精确的做法。但必须清楚:PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) 是聚合函数,不是窗口函数。它不能直接加 OVER,否则语法错误。
- 正确写法(每组一个中位数值):
SELECT deptno, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_sal FROM emp GROUP BY deptno;
- 如果非要每行都带中位数(例如做同比计算),需用子查询或
LATERAL:SELECT e.*, m.median_sal FROM emp e JOIN (SELECT deptno, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_sal FROM emp GROUP BY deptno) m ON e.deptno = m.deptno;
- 排序时没写
NULLS LAST?可能把NULL排到中间位置,导致结果偏差——PERCENTILE_CONT虽忽略NULL计算,但ORDER BY的行为会影响实际参与排序的行集。
MySQL / SQL Server 必须手写逻辑:用 ROW_NUMBER() + COUNT()
核心思路是定位中间行号:对每组按值排序,算总行数 cnt,取第 FLOOR((cnt+1)/2) 和 CEILING((cnt+1)/2) 行的平均值(兼容奇偶)。
- 关键陷阱:
ROW_NUMBER()给重复值分配不同序号,RANK()则会并列跳号——选错会导致中位位置偏移。例如值为[1,2,2,3],ROW_NUMBER()给出[1,2,3,4],中位是第 2&3 行(即 2 和 2),平均仍是 2;但若用RANK(),结果是[1,2,2,4],中间序号变成 2 和 3,就错了。 - 性能敏感点:没在
PARTITION BY group_col, value上建联合索引,ORDER BY排序会变慢,尤其数据量大时。 - 边界情况:某组只有一行且值为
NULL,最终结果就是NULL,不是 0 或默认值——别指望数据库自动补。
别盲目追求“每行都有中位数”
多数报表或 BI 场景真正需要的,只是每个分组一个中位数值。强行用窗口逻辑让每行重复输出,不仅增加计算开销,还容易让下游误以为该值可随行变化(比如拿去做 WHERE median_sal > 1000 过滤),而实际上它是静态聚合结果。更隐蔽的问题是:中位数对空值和重复值极其敏感,但很多手写方案连 NULL 过滤都没做,直接参与排序,结果已经失真了。

















