中位数需用percentile_cont或percentile_disc实现,不可直接用median();前者插值计算(连续分布),后者取实际值(更符合传统定义);必须配合over()或within group使用,且须通过子查询先计算再过滤。

中位数不是内置函数,得用 PERCENTILE_CONT 或 PERCENTILE_DISC
SQL 标准里没有 MEDIAN() 这种直接函数,PostgreSQL、Oracle、SQL Server(2022+)、Snowflake 支持 PERCENTILE_CONT 和 PERCENTILE_DISC,但行为不同:PERCENTILE_CONT 插值计算(适合连续分布),PERCENTILE_DISC 取实际存在的值(更接近传统中位数定义)。MySQL 和旧版 SQL Server 则得靠变量或窗口函数模拟。
关键点:必须搭配 OVER() 窗口子句,且不能直接写在 WHERE 里——因为聚合/窗口函数不能出现在过滤条件中,必须先算出中位数,再用于比较。
用子查询先算中位数,再和原表关联过滤
这是最通用、兼容性最好的写法,适用于所有支持窗口函数的数据库(包括 MySQL 8.0+):
SELECT t.value FROM numbers t CROSS JOIN ( SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) AS med FROM numbers ) m WHERE t.value > m.med;
注意几个细节:
-
PERCENTILE_CONT(0.5)中的0.5是硬编码,不能用变量替代(多数引擎不支持) -
WITHIN GROUP (ORDER BY value)必须指定排序字段,且类型需支持比较(比如不能对TEXT直接用,除非有明确排序规则) - 如果原始表有
NULL,默认会被忽略;若想包含,得提前COALESCE或WHERE value IS NOT NULL - 结果可能为空——当中位数等于最大值,或所有值都相等时
PERCENTILE_CONT vs PERCENTILE_DISC:选错会漏掉记录
假设数据是 [1, 2, 3, 4]:
-
PERCENTILE_CONT(0.5)返回2.5(插值),value > 2.5得到3, 4 -
PERCENTILE_DISC(0.5)返回2(取第 2 小的值),value > 2也是3, 4
但如果是 [1, 2, 3, 4, 5]:
-
PERCENTILE_CONT(0.5)→3.0,> 3.0得4, 5 -
PERCENTILE_DISC(0.5)→3(中间那个),> 3仍是4, 5
真正容易出问题的是偶数个 NULL 或重复值多的场景。例如 [1, 1, 1, 1]:PERCENTILE_DISC 返回 1,value > 1 结果为空;而 PERCENTILE_CONT 也返回 1.0,结果一样。但如果你本意是“大于等于中位数”,就得换成 >=,且明确自己要的是哪个语义。
MySQL 8.0 以下只能靠行号模拟,性能差还易错
没窗口函数时,常见写法是用变量生成序号,再取总行数一半的位置:
SELECT value FROM (
SELECT value,
@row := @row + 1 AS row_num,
@cnt AS total
FROM numbers
CROSS JOIN (SELECT @row := 0, @cnt := COUNT(*) FROM numbers) init
ORDER BY value
) t
WHERE row_num > CEIL(total / 2);
这个方案问题很多:
- 变量赋值顺序依赖执行计划,MySQL 8.0.22+ 已标记为不可靠
-
CEIL(total / 2)对偶数长度取上中位位置,但中位数定义本身有歧义(下中位?均值?) - 无法处理重复值并列的情况(比如 100 个
5全挤在中间,该不该全算进“大于中位数”?) - 大数据量时
ORDER BY+ 全表扫描开销明显
真遇到老 MySQL,优先考虑应用层计算中位数,或者升级。
嵌套百分位函数本身不难,难的是确认你用的到底是哪种中位数、数据有没有空值和重复、目标数据库版本支不支持标准语法——这些不厘清,查出来的“大于中位数”的结果,可能根本不是你想要的那批记录。










