中位数不是聚合函数,SQL标准不支持MEDIAN()用于WHERE或HAVING;需先用ROW_NUMBER()和COUNT()计算每组中位数值,再通过子查询或JOIN过滤大于该值的记录。

中位数不是聚合函数,不能直接用 MEDIAN() 过滤
SQL 标准里没有 MEDIAN() 聚合函数(Oracle 有但不可用于 HAVING 或窗口过滤),所以“找出每个分组中中位数以上的记录”本质是:先算出每组的中位数,再把原表和它关联/比较。直接写 WHERE value > MEDIAN(value) OVER (PARTITION BY group_id) 会报错——窗口函数不能出现在 WHERE,且多数数据库不支持该语法。
用窗口函数 + 行号模拟中位数位置更可靠
核心思路是:对每组按值排序,取中间位置的行(或中间两行的平均值),但“中位数以上”只需判断当前行是否严格大于该组中位数。推荐用 ROW_NUMBER() 和 COUNT() OVER 算出中位数索引位置,再比大小:
SELECT group_id, value
FROM (
SELECT group_id, value,
COUNT(*) OVER (PARTITION BY group_id) AS cnt,
ROW_NUMBER() OVER (PARTITION BY group_id ORDER BY value) AS rn
FROM t
) t2
WHERE value > (
SELECT AVG(value)
FROM (
SELECT value,
ROW_NUMBER() OVER (ORDER BY value) AS rn,
COUNT(*) OVER () AS cnt
FROM t t3
WHERE t3.group_id = t2.group_id
) mid
WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0))
);
- 子查询里用
FLOOR((cnt + 1) / 2.0)和CEIL(...)覆盖奇偶两种情况(如 cnt=5 → rn=3;cnt=4 → rn=2,3) - 外层
value > (SELECT AVG(...))是关键:先算出中位数,再过滤,避免用排名近似替代真实值 - 注意:若存在重复值,
ROW_NUMBER()会强制排序去重,但中位数定义基于值本身,不是位置——所以必须用值计算,不能只靠rn > cnt/2
PostgreSQL 可用 PERCENTILE_CONT(0.5) 简化
PostgreSQL 支持连续百分位函数,能直接返回中位数值,配合 LATERAL 或 CTE 更清晰:
SELECT t.group_id, t.value FROM t JOIN LATERAL ( SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) AS med FROM t t2 WHERE t2.group_id = t.group_id ) m ON t.value > m.med;
-
PERCENTILE_CONT(0.5)返回的是插值中位数(如 [1,2,3,4] → 2.5),符合数学定义 - MySQL 8.0+ 没有等价函数,需用
ROW_NUMBER()+COUNT()手动实现 - SQL Server 用
PERCENTILE_CONT(0.5) OVER (PARTITION BY group_id ORDER BY value)但只能用于 SELECT 列,不能直接在 WHERE 用,仍需 CTE 包裹
性能和空值要提前处理
中位数对 NULL 敏感:默认排序时 NULL 可能排最前或最后,导致计数和排名错位。
- 务必在排序字段加
WHERE value IS NOT NULL,或用ORDER BY value NULLS LAST(PostgreSQL/Oracle)显式控制 - 大表慎用多次子查询嵌套,可先用 CTE 预算每组中位数:
WITH medians AS (SELECT group_id, PERCENTILE_CONT(0.5) ...) - 如果只要“上半部分记录”(不严格要求 > 中位数),用
rn > cnt / 2会快很多,但注意这是按行数切分,不是按值——当分布偏斜时结果可能明显偏离预期
真正卡住的往往不是怎么写,而是没意识到:中位数是标量值,不是排名位置;而 SQL 的 WHERE 无法直接消费窗口计算出的标量,必须把它落地成 JOIN 条件或子查询结果。











