不能直接用 group by + count 拿众数频率,因为 sql 不允许嵌套聚合函数如 max(count(*)),必须先按分组和值统计频次,再对每组频次取最大值。

为什么不能直接用 GROUP BY + COUNT 拿众数频率
因为众数频率是「每个分组内出现次数最多的那个值的计数」,不是整个表的最高频次。单层 GROUP BY 只能算出每个值在组内的频次,但无法从中挑出最大值——SQL 不允许在聚合结果上再套一层 MAX(COUNT(...)),会报错 ERROR: aggregate function calls cannot be nested。
必须拆成两步:先按分组+值统计频次,再对每个分组取这个频次的最大值。
标准写法:子查询嵌套两层 GROUP BY
核心思路是把「值→频次」作为中间结果,再按分组维度聚合一次求 MAX:
SELECT group_col, MAX(freq) AS mode_freq FROM ( SELECT group_col, value_col, COUNT(*) AS freq FROM your_table GROUP BY group_col, value_col ) t GROUP BY group_col;
注意点:
-
value_col必须参与内层GROUP BY,否则无法统计每个值的出现次数 - 外层
GROUP BY group_col是为了按原始分组汇总,不能漏 - 如果表里有
NULL值,默认被COUNT(*)计入,但COUNT(value_col)会跳过,按需选择
MySQL 8.0+ 或 PostgreSQL 可用窗口函数简化
避免子查询嵌套,用 ROW_NUMBER() 或 RANK() 标记每组内频次排名,再过滤第一:
SELECT group_col, freq AS mode_freq
FROM (
SELECT group_col, freq,
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY freq DESC) AS rn
FROM (
SELECT group_col, value_col, COUNT(*) AS freq
FROM your_table
GROUP BY group_col, value_col
) t
) ranked
WHERE rn = 1;
区别说明:
-
ROW_NUMBER():严格取一个(并列时随机选一),RANK()会保留并列,导致一行变多行 - 窗口函数版本性能通常更好,尤其数据量大时,避免物化中间结果集
- SQLite 不支持窗口函数(除非 3.25+ 且启用),老版本得退回子查询方案
容易忽略的边界情况
众数频率本身不关心具体是哪个值,但实际业务中常需要连带输出众数值(mode value)。这时不能只靠 MAX(freq),而要确保 value_col 和 freq 绑定一致:
- 直接
SELECT group_col, MAX(freq), value_col会报错或返回错误的value_col(非对应最大频次的那个) - 正确做法是用
MAX(freq)结果去关联原频次表,或改用MODE() WITHIN GROUP(PostgreSQL 仅支持) - 空组(某
group_col下全为NULL或无数据)会导致外层GROUP BY不返回该组,需提前补零逻辑
两层聚合本身不难,难的是意识到「众数频率」是个派生指标,必须经过中间频次表——漏掉这层抽象,就只能硬编码 CASE WHEN 或放弃通用性。











