MySQL 8.0.13+ 可用 PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY col) OVER() 精确计算中位数,需配合 ORDER BY 且仅支持常量分位点,返回插值结果而非近似值。
MySQL 8.0+ 怎么用
算中位数
MySQL 原生不支持
,但 8.0.13 起引入了窗口函数
,能真正按连续分布算中位数。它不是近似值,而是插值结果,比如 [1,3,5,7] 的中位数是 4.0((3+5)/2),
就会返回这个。
实操注意三点:
必须配合
窗口子句,不能直接写在
普通字段里
必须指定
,否则报错
它只支持常量表达式作为分位点,不能写成
或列名
示例:查订单金额中位数
PostgreSQL 用
还是
PostgreSQL 同时提供两个函数:
插值计算(返回浮点),
取实际存在的离散值(返回同类型)。选哪个取决于业务语义。
比如用户年龄字段是整数,用
返回的一定是某个真实用户的年龄;而用
可能返回 32.5 —— 这在统计报告里可以接受,但在生成“典型用户画像”时可能不合适。
常见误用:
忘记加
,报错
在聚合查询里混用窗口函数,比如
同时又用
,导致结果行数爆炸
对空值没处理,
会被自动忽略,但若全列是
,函数返回
,不是报错
SQLite 和旧版 MySQL 怎么硬算中位数
SQLite 没有分位函数,MySQL 5.7 及更早版本也不支持窗口函数。这时得靠排序 + 行号模拟,核心思路是:取第
和
行的平均值。
难点不在逻辑,而在兼容性写法:
MySQL 5.7 用变量
排序后编号,但必须确保
在子查询里完成,否则变量执行顺序不可靠
SQLite 3.25+ 支持
,但老版本只能用自连接计数,性能极差,10 万行以上基本卡死
所有手写方案都对
敏感:如果排序字段含
,不同数据库的
默认行为不同(MySQL 把
排最前,PostgreSQL 排最后)
MySQL 5.7 安全写法示例(显式排除
):
为什么
在某些场景不如手动取中间两行
分位函数看着省事,但实际容易掩盖数据分布问题。比如一组数据是 [1,1,1,1,100],中位数是 1,但
也返回 1 —— 这没问题;可如果业务真正关心的是“去掉异常值后的中心趋势”,那中位数本身就不够用。
更关键的是性能差异:
内部要对整个分区排序,无法利用索引加速
(即使字段有索引,窗口函数仍会触发临时排序)
大数据量下,手写方案如果能先用
配合子查询缩小范围(比如只取 top 10000),反而更快
某些 OLAP 场景需要同时算多个分位点(p10/p50/p90),用一个
数组语法(如 PostgreSQL 的
)比多次扫描高效,但 MySQL 不支持数组参数
真正麻烦的不是语法怎么写,而是你得先确认:这个“中位数”是要进报表、做监控阈值,还是给下游模型当特征?不同用途,对精度、延迟、空值容忍度的要求天差地别。
PERCENTILE_CONTMEDIAN()PERCENTILE_CONTPERCENTILE_CONT(0.5)PERCENTILE_CONTOVER()SELECTORDER BYWindow 'w' requires an ORDER BY clausePERCENTILE_CONT(@p)SELECT DISTINCT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) OVER() AS median_amount
FROM orders;PERCENTILE_CONTPERCENTILE_DISCPERCENTILE_CONTPERCENTILE_DISCPERCENTILE_DISCPERCENTILE_CONTORDER BYERROR: PERCENTILE_CONT requires an ORDER BY clauseGROUP BY user_idPERCENTILE_CONT() OVER()NULLNULLNULLFLOOR((n+1)/2)CEIL((n+1)/2)@row := @row + 1ORDER BYROW_NUMBER()NULLNULLORDER BYNULLNULLSELECT AVG(amount) AS median
FROM (
SELECT amount,
@row := @row + 1 AS row_num
FROM (SELECT amount FROM orders WHERE amount IS NOT NULL ORDER BY amount) t,
(SELECT @row := 0) r
) ranked
WHERE row_num IN (FLOOR((@row + 1) / 2), CEIL((@row + 1) / 2));PERCENTILE_CONT(0.5)PERCENTILE_CONT(0.5)PERCENTILE_CONTORDER BYLIMITPERCENTILE_CONTPERCENTILE_CONT(ARRAY[0.1,0.5,0.9])