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

