PostgreSQL中GROUP BY后获取每组众数需分两步:先按组和值分组计数,再用ROW_NUMBER()或RANK()取频次最高者;9.5+通用写法为子查询+ORDER BY COUNT(*) DESC LIMIT 1,14+支持mode() WITHIN GROUP但仅限可排序类型且返回排序靠前的一个。

GROUP BY 后怎么拿到每组的众数(mode)
SQL 标准语法里没有直接的 MODE() 聚合函数(PostgreSQL 9.4+ 有 MODE() WITHIN GROUP,但多数场景不适用),所以不能靠单个聚合函数一步到位。必须拆成两步:先统计频次,再对每组取频次最高的那个值——本质是「分组内 Top 1 by count」问题。
用窗口函数 + 子查询是最通用解法
核心思路:先按 GROUP BY 和目标字段组合计数,再用 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY COUNT(*) DESC) 给每组内的值排频次名次,最后外层筛选 rn = 1。
假设表 sales 有字段 region(地区)、product(产品),想查每个 region 中销量最多的 product:
SELECT region, product
FROM (
SELECT region, product,
ROW_NUMBER() OVER (
PARTITION BY region
ORDER BY COUNT(*) DESC, product ASC
) AS rn
FROM sales
GROUP BY region, product
) t
WHERE rn = 1;注意点:
-
ORDER BY COUNT(*) DESC, product ASC中的product ASC是为了解决频次并列时的不确定性——否则不同执行可能返回不同值;按字典序补排序能保证结果稳定 - 如果只要一个值,用
ROW_NUMBER();如果允许返回多个并列最高频的值,改用RANK()或DENSE_RANK() - MySQL 8.0+、PostgreSQL、SQL Server、Oracle 都支持;SQLite 不支持窗口函数,需另寻方案
MySQL 5.7 或旧版 SQLite 怎么办
这类不支持窗口函数的引擎,得靠相关子查询或连接模拟。效率较低,数据量大时明显变慢。
典型写法(以 MySQL 5.7 为例):
SELECT s1.region, s1.product
FROM sales s1
GROUP BY s1.region, s1.product
HAVING COUNT(*) = (
SELECT MAX(cnt)
FROM (
SELECT COUNT(*) AS cnt
FROM sales s2
WHERE s2.region = s1.region
GROUP BY s2.product
) t
);隐患很明确:
- 子查询会为每一组重复执行,N 组就是 N 次全表扫描(或索引扫描)
- 若某组有多个 product 并列最高频,这个写法会全部返回;而用
ROW_NUMBER()的版本默认只取一个 - 必须确保
(region, product)上有复合索引,否则性能雪崩
为什么不能直接用 GROUP BY + MAX(COUNT(*))
因为 COUNT(*) 是聚合函数,MAX(COUNT(*)) 属于「聚合函数嵌套聚合函数」,SQL 标准禁止这种写法,所有主流数据库都会报错:ERROR: aggregate function calls cannot be nested(PostgreSQL)、Invalid use of group function(MySQL)。
常见误写:
SELECT region, MAX(COUNT(*)) FROM sales GROUP BY region; -- ❌ 语法错误
正确方向永远是:先完成底层分组计数(即生成「组 × 值 × 频次」中间结果),再在这张中间结果上做二次筛选。
真正容易被忽略的是稳定性控制——频次相同时是否接受任意一个?还是必须确定性地选最小/最大值?这直接影响 ORDER BY 的第二字段要不要加、加什么。线上逻辑一旦依赖不确定行为,后续排查会非常被动。

















