postgresql中用percentile_cont(0.5)计算中位数必须配合within group (order by ...)聚合语法,如select dept, percentile_cont(0.5) within group (order by salary) from emp group by dept;漏写order by会报错,且不可混用group by与over窗口语法。

PostgreSQL 用 percentile_cont 直接算中位数
PostgreSQL 是少数原生支持中位数计算的数据库,percentile_cont(0.5) 就是标准解法。它对每个分组排序后做线性插值,能处理偶数个值(比如 [2,4] 返回 3.0),结果是 numeric 类型。
常见错误是漏写 WITHIN GROUP (ORDER BY ...),直接写 percentile_cont(0.5) OVER (PARTITION BY ...) 会报错 window function call requires a frame specification —— 这不是窗口函数用法,必须用聚合形式。
- 正确写法:
SELECT dept, percentile_cont(0.5) WITHIN GROUP (ORDER BY salary) AS median FROM emp GROUP BY dept - 若要整数结果,加
::integer强转,但注意四舍五入逻辑(3.7::integer → 3) - 性能上,
ORDER BY字段最好有索引,否则大表分组排序开销明显
MySQL 8.0+ 用 ROW_NUMBER() + 分组计数模拟中位数
MySQL 没内置中位数函数,得靠行号和总数推位置。核心思路:给每组按数值排序编号,再找出中间一或两个位置的值。
容易踩的坑是没处理奇偶差异,或误用 GROUP BY 和窗口函数顺序——必须先用子查询/CTE 排好序、标好号,再在外层按组聚合。
- 关键步骤:先
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary),再用COUNT(*) OVER (PARTITION BY dept)算每组总数 - 取值逻辑:
WHERE rn IN (FLOOR((cnt+1)/2), CEIL((cnt+1)/2)),覆盖奇数(取中间1个)和偶数(取中间2个平均) - 别在
WHERE里直接写窗口函数,MySQL 不允许;必须放子查询里过滤
众数要用 COUNT(*) 分组排序取 Top 1
众数本质是频次最高的值,不是排序中间值。所有 SQL 方言都得靠「按值分组 → 计数 → 降序 → 取首行」三步走,但细节差异大。
问题常出在并列众数处理:比如 [1,1,2,2,3] 里 1 和 2 都出现两次,是否都要返回?多数业务只要一个,但得明确用 LIMIT 1 还是 FETCH FIRST 1 ROW ONLY,否则可能报错或返回多行。
- 通用写法(以 PostgreSQL 为例):
SELECT val FROM tbl GROUP BY val ORDER BY COUNT(*) DESC LIMIT 1 - MySQL 8.0+ 同样适用,但旧版不支持
LIMIT在子查询里直接用,得套一层(SELECT ...) - 如果真要返回全部众数,得先算出最大频次
MAX(cnt),再用HAVING COUNT(*) = max_cnt,但性能较差
SQLite 和旧版 MySQL 只能靠自连接或子查询硬算
这些引擎不支持窗口函数,中位数必须用自连接比大小或相关子查询数“左边有多少个 ≤ 当前值”,效率低且易超时。
例如 SQLite 中找中位数:对每个值,统计小于等于它的数量,再和总数一半比较。当表行数大时,O(n²) 复杂度立刻暴露——10 万行可能卡住。
- 避免方案:提前把数据导出到 Python/JS 做计算,SQL 只负责分组聚合
- 如果非得在库内算,优先用应用层缓存中位数结果,而不是每次查都重算
- 众数相对友好,
GROUP BY + ORDER BY COUNT(*) DESC LIMIT 1在 SQLite 里完全可用
ORDER BY 默认排最后)、重复值是否影响行号、浮点精度截断……众数更麻烦——业务上“出现最多”可能指“首次出现最多的”,而非单纯频次。这些细节不会报错,但结果可能和预期差很远。











