国内海外服务器测评及优惠
Linux服务器运维救灾服务

SQL 如何计算中位数(Median)的跨数据库写法

MySQL 8.0+需用ROW_NUMBER()与COUNT()配合求中位数;PostgreSQL直接支持PERCENTILE_CONT(0.5) WITHIN GROUP;SQL Server推荐OFFSET/FETCH法;SQLite依赖子查询,性能差;各库对NULL处理逻辑不一致。

MySQL 8.0+ 直接用 PERCENTILE_CONT 函数

MySQL 8.0.13 起支持窗口版的 PERCENTILE_CONT,但注意它只在 SELECT 列表中作为聚合窗口函数可用,不能直接用于 GROUP BY 后的独立中位数计算(会报错“Invalid use of dow function”)。正确写法是配合 ROW_NUMBER() 和计数判断:

SELECT AVG(val) AS median
FROM (
  SELECT val,
         ROW_NUMBER() OVER (ORDER BY val) AS rn,
         COUNT(*) OVER () AS cnt
  FROM numbers
) t
WHERE rn IN (FLOOR((cnt + 1) / 2), CEIL((cnt + 1) / 2));

这个写法兼容性好,适用于所有 MySQL 8.0+ 版本。注意:FLOORCEIL 处理奇偶逻辑必须同时覆盖两个位置——偶数长度时取中间两个,奇数时两个位置重合。

PostgreSQL 用 PERCENTILE_CONT(0.5) 最简洁

PostgreSQL 原生支持聚合版 PERCENTILE_CONT,可直接在 GROUP BY 或子查询中使用,无需手动算行号:

SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY val) AS median
FROM numbers;

如果按分组求中位数(比如每个 catery 的中位数),加 GROUP BY category 即可。注意:WITHIN GROUP 是强制语法,漏写会报错 syntax error at or near "ORDER";另外该函数对 NULL 值自动过滤,和 COUNT 行为一致。

SQL Server 需用 PERCENT_RANK() 或双 OFFSET

SQL Server 不支持 PERCENTILE_CONT (仅窗口版,且不能直接用于中位数),稳妥做法是用 OFFSET/FETCH 分两步取中间值:

万兴爱画AI绘画生成工具

DECLARE @cnt INT = (SELECT COUNT(*) FROM numbers WHERE val IS NOT NULL);
SELECT AVG(1.0 * val) AS median
FROM (
  SELECT val
  FROM numbers
  WHERE val IS NOT NULL
  ORDER BY val
  OFFSET (@cnt - 1) / 2 ROWS
  FETCH NEXT 2 - @cnt % 2 ROWS ONLY
) t;

关键点:OFFSET (@cnt - 1) / 2 把起始偏移统一为 0-based 中间位置;FETCH NEXT 2 - @cnt % 2 控制取 1 行(奇数)或 2 行(偶数)。若用 PERCENT_RANK() 窗口函数,则需额外处理 0.5 精确匹配失败的情况(因浮点精度,常需 ABS(pr - 0.5) 近似)。

SQLite 没有窗口函数,靠子查询 + (SELECT COUNT...) 模拟

SQLite 3.25+ 支持窗口函数,但旧版本仍占多数。通用写法是用相关子查询统计小于等于当前值的记录数,再比对总数一半:

SELECT AVG(val) AS median
FROM numbers n1
WHERE (
  SELECT COUNT(*)
  FROM numbers n2
  WHERE n2.val <= n1.val
) BETWEEN (SELECT COUNT(*) FROM numbers) / 2.0 AND (SELECT COUNT(*) FROM numbers) / 2.0 + 1;

性能差是硬伤:对每行都执行两次全表扫描,数据量超千行就明显变慢。若确定数据无重复,可用更高效写法(基于排序后 rowid),但需先建临时有序表——实际项目中建议导出到 Python 或用 CLI 加 .mode csv 后用外部算。

跨库写中位数最易被忽略的是 NULL 值处理逻辑不一致:MySQL/PostgreSQL 自动忽略,SQL Server 默认包含(导致计数偏差),SQLite 子查询里 会把 NULL 当最小值参与比较。上线前务必用含 NULL 的测试数据跑一遍。

赞(0) 打赏
未经允许不得转载:linuxcto运维 » SQL 如何计算中位数(Median)的跨数据库写法

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫