MySQL 8.0.33+ 支持 MEDIAN() 窗口函数,但仅限于 OVER(PARTITION BY ... ORDER BY ...) 形式,不可用于 GROUP BY;它返回每组内按序排列的累积中位数,非传统分组独立中位数,且不支持 DISTINCT 或嵌套聚合。
MySQL 8.0+ 怎么用
计算分组中位数
MySQL 8.0.33 起才原生支持
窗口函数,但仅限于窗口模式(不能直接在
中用),且必须配合
。常见错误是写成
—— 这会报错
。
正确做法是先用窗口函数为每组排序编号,再取中间位置的值;或者改用
,但注意:它返回的是「累积中位数」(cumulative median),不是每组独立中位数。
在 MySQL 中不接受
,也不能嵌套在聚合上下文中
若数据量大,
可能比手动模拟慢,因需全组排序
空值(
)会被自动过滤,不影响中位数计算逻辑
PostgreSQL 怎么安全地用
替代
PostgreSQL 没有
函数,但
是标准、可靠、支持分组的替代方案。它在
中可直接使用,语义清晰,且对偶数个元素自动插值(如 [1,3] → 2.0)。
容易踩的坑是漏掉
子句,或把
写成列别名(如
),而实际只能用原始列或表达式。
必须写成
,括号和子句缺一不可
若分组后某组全为
,结果为
,不会报错
性能上,它依赖索引加速排序;没索引时,大数据量分组可能显著变慢
SQLite 和旧版 MySQL 怎么手动算分组中位数(无内置函数)
SQLite 完全没有中位数函数,旧版 MySQL(MEDIAN(),这时得靠变量或自连接模拟「第 N/2 小的值」。核心思路是:对每组按值排序,给每行打序号,再筛选序号落在中位区间的行。
例如 MySQL 5.7 的典型写法:
这个写法问题不少:依赖用户变量执行顺序(MySQL 8.0+ 已不推荐)、无法并行、空值处理模糊、且
对奇偶逻辑要小心匹配。
SQLite 推荐用两次
窗口(需 3.25+):一次正序、一次倒序,找两个序号都 ≥ 总数一半的行
所有手动实现都默认忽略
;若需保留,得先用
或子查询预处理
当某组只有一行时,确保
和
结果一致,否则会漏数据
为什么不能直接用
或
代替中位数
中位数的核心价值是抗异常值,而
不是替代品——哪怕只是想“快速近似”,在偏态分布下误差可能极大(比如 99 个 1 和 1 个 1000,
≈ 10.99,
= 1)。
在 PostgreSQL 中存在,但它返回「离散值」(即组内真实存在的某个值),不插值。例如 [1,2,4,5] 返回 2 而非 3.0。这和多数人理解的中位数定义(连续型)不一致,尤其在统计报表中易引发歧义。
在偶数长度时不保证返回哪个中间值(PostgreSQL 文档注明“implementation-defined”)
某些 BI 工具(如 Metabase)的「中位数」字段其实是调用
,但导出 SQL 时可能悄悄替换成
,务必检查生成语句
跨数据库移植时,
行为差异最大:Oracle 返回近似值,MySQL 8.0.33+ 返回精确值,而 SQL Server 根本没这个函数
真正麻烦的不是语法怎么写,而是确认你用的数据库版本是否真的支持、以及那个“支持”是不是你理解的支持——比如 MySQL 的
看似来了,实则不能直接进
,还得绕一圈。动手前,先
和查文档确认行为细节,比抄示例代码重要得多。
MEDIAN()MEDIAN()GROUP BYOVER()SELECT MEDIAN(score) FROM t GROUP BY classERROR 1111 (HY000): Invalid use of group functionMEDIAN() OVER (PARTITION BY class ORDER BY score)MEDIAN()DISTINCTMEDIAN() OVER (...)NULLPERCENTILE_CONT(0.5)MEDIAN()MEDIAN()PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x)GROUP BYWITHIN GROUPORDER BYORDER BY avg_scorePERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score)NULLNULLSELECT class, AVG(score) AS median
FROM (
SELECT class, score,
@row := IF(@prev = class, @row + 1, 1) AS row_num,
@prev := class
FROM t, (SELECT @row := 0, @prev := '') r
ORDER BY class, score
) ranked
JOIN (
SELECT class, COUNT(*) AS cnt FROM t GROUP BY class
) cnts USING (class)
WHERE row_num IN (FLOOR((cnt + 1)/2), CEIL((cnt + 1)/2))
GROUP BY class;FLOOR/CEILROW_NUMBER()NULLCOALESCEFLOORCEILAVG()PERCENTILE_DISCAVG()AVGMEDIANPERCENTILE_DISC(0.5)PERCENTILE_DISCPERCENTILE_CONTAVGMEDIAN()MEDIAN()GROUP BYSELECT VERSION()