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 window 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+ 版本。注意:FLOOR 和 CEIL 处理奇偶逻辑必须同时覆盖两个位置——偶数长度时取中间两个,奇数时两个位置重合。
PostgreSQL 用 PERCENTILE_CONT(0.5) 最简洁
PostgreSQL 原生支持聚合版 PERCENTILE_CONT,可直接在 GROUP BY 或子查询中使用,无需手动算行号:
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY val) AS median FROM numbers;
如果按分组求中位数(比如每个 category 的中位数),加 GROUP BY category 即可。注意:WITHIN GROUP 是强制语法,漏写会报错 syntax error at or near "ORDER";另外该函数对 NULL 值自动过滤,和 COUNT 行为一致。
SQL Server 需用 PERCENT_RANK() 或双 OFFSET
SQL Server 不支持 PERCENTILE_CONT 聚合函数(仅窗口版,且不能直接用于中位数),稳妥做法是用 OFFSET/FETCH 分两步取中间值:
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 的测试数据跑一遍。










