postgresql 14+ 支持内置 mode() 聚合函数,需配合 within group (order by col) 使用,仅返回单个众数(并列时取排序首值),不支持窗口用法;旧版本须用 group by + count() + row_number() 或 rank() 等手动实现。

PostgreSQL 14+ 直接用 mode() 窗口函数最简单
PostgreSQL 14 引入了内置聚合函数 mode(),它能直接返回分组内出现频次最高的值(众数)。注意:它只返回一个值,即使存在多个并列最高频次的值,也只取排序后第一个(按数据类型的默认顺序)。
常见错误是误以为 mode() 是普通标量函数——它必须配合 GROUP BY 使用,且不能单独写在 SELECT 列表里不加 GROUP BY(会报错:ERROR: column "x" must appear in the GROUP BY clause)。
- 语法必须是:
SELECT ..., mode() WITHIN GROUP (ORDER BY col) FROM tbl GROUP BY ... -
ORDER BY子句不可省略,且决定了并列时选哪个值(例如对字符串按字典序,对数字按升序) - 如果某组所有值唯一(频次全为 1),
mode()返回该组第一个排序值(不是 NULL) - 空组或全 NULL 的组会返回 NULL
示例:
SELECT category, mode() WITHIN GROUP (ORDER BY rating) AS most_common_rating FROM products GROUP BY category;
PostgreSQL 13 及更早版本得靠 array_agg() + unnest() + GROUP BY 模拟
旧版本没有 mode(),只能手动统计频次。核心思路是:先对每组生成值频次表,再取频次最高的那个值。容易踩的坑是忽略「多值并列众数」的处理逻辑——多数业务场景其实只需要任取其一,但若需全部返回,就得用数组或 JSON 聚合。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
- 最简稳妥做法(返回单个众数):
SELECT category, (ARRAY_AGG(value ORDER BY cnt DESC, value))[1] FROM (...) GROUP BY category - 子查询里必须同时按频次
cnt DESC和原始值排序(如value),否则并列时结果不稳定 - 别直接用
MAX()或MIN()替代——它们不反映频次,纯按值大小选,和众数无关 - 如果字段含 NULL,
GROUP BY默认忽略 NULL 行;需要包含 NULL 众数,得显式用GROUP BY col IS NULL, col
示例(单值众数):
SELECT category,
(ARRAY_AGG(rating ORDER BY freq DESC, rating))[1] AS mode_rating
FROM (
SELECT category, rating, COUNT(*) AS freq
FROM products
WHERE rating IS NOT NULL
GROUP BY category, rating
) t
GROUP BY category;
遇到 NULL 或重复频次时,mode() 的行为必须提前确认
众数定义本身在存在多个最高频值时就有歧义。PostgreSQL 的 mode() 明确选择「排序后第一个」,但这未必符合你的业务预期。比如分组中 'low' 和 'high' 各出现 5 次,按字典序 'high' 排前面,mode() 就返回它——而你可能期望报错、返回数组,或随机选一个。
- 若需返回所有众数,必须手写子查询 + 窗口函数:
RANK() OVER (PARTITION BY category ORDER BY COUNT(*) DESC),再过滤 RANK = 1 - 若字段类型不支持排序(如某些自定义类型),
mode() WITHIN GROUP (ORDER BY ...)会报错,得先转成可排序类型(如 cast 为 text) - NULL 值参与排序时排在最前(
NULLS FIRST默认),所以若允许 NULL 为众数,确保ORDER BY col NULLS LAST不意外排除它
性能敏感场景下,mode() 并不自动走索引
mode() 是纯内存聚合操作,不利用索引加速。当分组数据量极大(如单组超百万行)时,WITHIN GROUP (ORDER BY ...) 的排序开销会显著上升。此时应优先考虑预计算或物化中间结果。
- 避免在高并发 OLTP 查询中对大表实时算众数;更适合离线统计或加物化视图
- 若只关心 TOP-N 频次值,用
APPROX_COUNT_DISTINCT或扩展topn(需安装)比精确mode()更快 - 在
WHERE中尽早过滤(如加时间范围、状态条件),减少进入GROUP BY的行数,比优化mode()本身更有效
真正麻烦的从来不是怎么写出众数,而是想清楚:当数据分布扁平、频次胶着、NULL 大量存在时,“众数”这个概念本身是否还承载业务意义。










