MODE()在PostgreSQL 14+中存在但不能直接用于分组众数计算,因其WITHIN GROUP仅对整体结果集排序取首频值;正确做法是用两层窗口函数:先COUNT(*) OVER (PARTITION BY group_col, value_col)统计频次,再ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY cnt DESC, value_col ASC)取每组最高频值。

MODE() 在 PostgreSQL 14+ 中确实存在,但它**不能直接用于分组众数计算**。你写 SELECT group_col, MODE() WITHIN GROUP (ORDER BY value_col) FROM t GROUP BY group_col 看似合理,实际返回的是全表众数重复 N 次——MODE() 是有序集聚合函数,不支持按 GROUP BY 切片后各自排序聚合,它只对整个输入集(或子查询结果)做一次排序+取首频值。
必须用窗口函数组合模拟,这是唯一跨版本、可复现、能真正按组算众数的方案。
为什么 MODE() WITHIN GROUP 不能配合 GROUP BY 做分组众数
根本原因在于执行顺序:GROUP BY 之后的 MODE() WITHIN GROUP 并不会“在每个组内独立执行”,而是把整个 GROUP BY 输出的结果集(N 行)当作一个整体再排序聚合。也就是说,它看到的是 N 行 (group_col, value_col) 对,而不是 N 个逻辑分组。
常见错误现象:
- 所有
group_col对应的mode()值完全一样 - 结果与去掉
GROUP BY直接全表跑MODE() WITHIN GROUP一致 - EXPLAIN 显示没有
HashAggregate或GroupAggregate节点参与 mode 计算
正确做法:两层窗口函数 + 稳定排序
核心是先按组+值统计频次,再在组内按频次降序排,取第 1 名。关键在排序必须稳定,否则同频值每次结果可能不同。
实操建议:
- 第一层:用
COUNT(*) OVER (PARTITION BY group_col, value_col)得到每个(group_col, value_col)组合在本组中出现次数 - 第二层:用
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY cnt DESC, value_col ASC)——value_col ASC不可省,否则同频时行为未定义 - 外层
WHERE rn = 1筛出每组最高频值(只返回一个) - 若字段含
NULL,需提前用COALESCE(value_col, 'NULL_MARKER')或显式GROUP BY group_col, value_col IS NULL, value_col处理,否则NULL被忽略
MySQL 8.0+/SQL Server/BigQuery 兼容写法示例
以下 SQL 在 PostgreSQL 14+、MySQL 8.0+、SQL Server 2017+、BigQuery 标准 SQL 中均可运行:
WITH freq AS (
SELECT group_col, value_col,
COUNT(*) OVER (PARTITION BY group_col, value_col) AS cnt
FROM t
),
ranked AS (
SELECT group_col, value_col,
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY cnt DESC, value_col ASC) AS rn
FROM freq
)
SELECT group_col, value_col AS mode_value
FROM ranked
WHERE rn = 1;
注意点:
- 不能把
COUNT(*) OVER和GROUP BY混在同一层——MySQL 8.0+ 会报错 “Invalid use of group function” - BigQuery 不支持
ORDER BY中用别名cnt,得写成COUNT(*) OVER (...) DESC - PostgreSQL 中若
value_col是text或jsonb,ASC 排序仍有效;但jsonb的相等比较需注意键序,建议先jsonb_strip_nulls()标准化
想返回所有并列众数?换 RANK() 不要 ROW_NUMBER()
ROW_NUMBER() 强制给同频值分配不同序号(1,2,3…),所以 rn = 1 只拿一个;而 RANK() 对相同 cnt 值赋予相同名次(如都是 1),后续跳过空位(1,1,3…)。
只需替换第二层窗口函数:
- 把
ROW_NUMBER()改成RANK() - 过滤条件保持
WHERE rn = 1 - 结果会返回该组所有最高频的
value_col(可能多行) - 性能略低(因需保留所有并列项),但语义更完整
真正容易被忽略的点是:业务上是否真的需要全部并列值?多数报表只要一个代表值即可,强行返回全部反而增加下游处理负担,且 RANK() 在大数据量下可能放大中间结果集。稳定性和可预期性,往往比“理论完备”更重要。

















