postgresql/oracle支持group by+within group聚合式分组,sql server必须用窗口函数加distinct去重,mysql 8.0.13+仅支持聚合式(不支持over),sqlite完全不支持;percentile_cont(0.5)线性插值得中位数(如[4,6]→5.0),percentile_disc(0.5)取实际值(如[4,6]→4),均须within group (order by col)且自动忽略null。

PERCENTILE_CONT 和 PERCENTILE_DISC 是计算分组百分位数的核心函数,但写法因数据库而异——PostgreSQL/Oracle 支持聚合式分组,SQL Server 必须用窗口函数加去重,MySQL 8.0.13+ 只支持聚合式(不支持 OVER),SQLite 完全不支持。
PostgreSQL/Oracle:直接用 GROUP BY + WITHIN GROUP
这是最简洁、性能最好的写法,一次扫描完成排序和分组聚合。
-
PERCENTILE_CONT(0.5)返回插值中位数(如 [4,6] → 5.0),PERCENTILE_DISC(0.5)返回实际存在的值(如 [4,6] → 4) - 必须写
WITHIN GROUP (ORDER BY salary),漏掉ORDER BY会报错:PERCENTILE_CONT requires an ORDER BY clause - NULL 值自动被忽略;若想让 NULL 参与排序,得提前用
COALESCE(salary, -1e9)归一化 - 不能混用非分组列和该函数,否则报错:
column "xxx" must appear in the GROUP BY clause
示例:
SELECT department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary,
PERCENTILE_DISC(0.75) WITHIN GROUP (ORDER BY salary) AS p75_salary
FROM employees
GROUP BY department;
SQL Server:只能用窗口函数 + DISTINCT 或子查询
PERCENTILE_CONT 在 SQL Server 中**不支持聚合形式**,写 GROUP BY 会直接报错:'PERCENTILE_CONT' is not a recognized built-in function name。
- 必须用
OVER (PARTITION BY dept ORDER BY salary),且WITHIN GROUP不可省略 - 每组所有行都会算出相同结果,所以需额外去重:用
DISTINCT最简,或用ROW_NUMBER()+WHERE rn = 1 - 兼容性要求:SQL Server 2012+,且数据库兼容级别 ≥ 110
- 如果分组字段有重复值(比如同一部门多条记录),
DISTINCT是安全的;但要注意ORDER BY中未包含的列不能出现在SELECT中,否则去重失效
示例(推荐):
SELECT DISTINCT department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER (PARTITION BY department) AS median_salary
FROM employees;
MySQL 8.0.13+:只支持聚合式,不支持 OVER 窗口
MySQL 的 PERCENTILE_CONT **只接受 WITHIN GROUP,完全不认 OVER**。写成窗口形式会静默返回 NULL 或语法错误。
- 必须搭配
GROUP BY使用,不能用于窗口场景(比如给每行打标“是否高于本组中位数”) - 空值自动跳过;若全为
NULL,结果为NULL,建议前置过滤:WHERE salary IS NOT NULL - 性能依赖
salary字段上的 B-tree 索引,否则每次执行都触发全表排序 - 不支持
APPROX_PERCENTILE_CONT(那是 SQL Server 2022+ / BigQuery 的)
示例:
SELECT department,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees
WHERE salary IS NOT NULL
GROUP BY department;
没函数可用时:手写 ROW_NUMBER() + COUNT(*) 模拟
SQLite、旧版 MySQL 或某些云数仓不支持 PERCENTILE_CONT,就得靠排序+行号硬算。
- 核心逻辑:先
ROW_NUMBER() OVER (ORDER BY x)排序,再用COUNT(*) OVER ()得总数,最后取中间位置(奇偶分别处理) - 注意:SQLite 3.25+ 才支持
ROW_NUMBER();老 MySQL 需用变量模拟,但并发下不可靠 - 容易漏掉
WHERE过滤 NULL,导致分母变大、结果偏移 - 性能差:大数据量时会生成临时排序结果集,比内置函数慢 3–10 倍
通用写法(PostgreSQL/SQL Server/MySQL 8.0+ 都可用):
SELECT AVG(salary) AS median_salary
FROM (
SELECT salary,
ROW_NUMBER() OVER (ORDER BY salary) AS rn,
COUNT(*) OVER () AS cnt
FROM employees
WHERE salary IS NOT NULL
) t
WHERE rn IN (FLOOR((cnt + 1) / 2.0), CEIL((cnt + 1) / 2.0));
真正麻烦的不是语法,而是不同数据库对同一个关键词(比如 WITHIN GROUP)的语义切割——它在 PostgreSQL 里是聚合入口,在 SQL Server 里是窗口函数的强制前缀,而在 MySQL 里则是唯一合法形态。写之前务必查清当前环境到底认哪种。










