MySQL 8.0+ 应用窗口函数 ROW_NUMBER() 按 category 分组内按 COUNT(*) 降序编号并取 rn=1;5.7-需相关子查询匹配每组最大频次;PostgreSQL 的 MODE() 不按频次统计,不适用该场景。

GROUP BY 后怎么取每组出现最多的值(mode)
直接用 COUNT() + ORDER BY + LIMIT 1 只能拿到**整个表中频次最高的那一条**,不是“每组内最多”。这是最常踩的坑——误以为加了 GROUP BY 就自动按组做 Top 1,其实 LIMIT 1 是全局生效的,和分组无关。
MySQL 8.0+ 推荐用窗口函数 ROW_NUMBER()
核心思路:先按组、按计数倒序排序,再取每组序号为 1 的记录。比子查询更清晰,也避免关联时的歧义。
示例:查每个 category 下销量最高的 product_name
SELECT category, product_name, cnt
FROM (
SELECT
category,
product_name,
COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY COUNT(*) DESC, product_name ASC) AS rn
FROM sales
GROUP BY category, product_name
) t
WHERE rn = 1;
-
PARTITION BY category表示按类别分组独立编号 -
ORDER BY COUNT(*) DESC, product_name ASC确保计数相同时有确定顺序(避免随机结果) - 如果某组有多个值并列最多,
ROW_NUMBER()只取一个;要全取得换RANK()或DENSE_RANK()
低版本 MySQL(5.7 及以下)必须用相关子查询
没有窗口函数时,只能对每组重复计算最大频次,再匹配。性能较差,数据量大时明显卡顿。
等价逻辑:
SELECT s1.category, s1.product_name, COUNT(*) AS cnt
FROM sales s1
GROUP BY s1.category, s1.product_name
HAVING COUNT(*) = (
SELECT MAX(cnt)
FROM (
SELECT COUNT(*) AS cnt
FROM sales s2
WHERE s2.category = s1.category
GROUP BY s2.product_name
) t
);
- 外层
GROUP BY必须包含所有非聚合字段(s1.category, s1.product_name) - 子查询里
WHERE s2.category = s1.category是关键,实现“相关”——否则就变成查全局最大频次了 - 注意:若某组所有
product_name都只出现 1 次,这个写法会返回全部,不是错误,是符合语义的
PostgreSQL / SQL Server 可用 MODE() 聚合函数(但有限制)
PostgreSQL 从 14 开始支持 MODE() WITHIN GROUP (ORDER BY ...),但只适用于单字段、且要求输入有序可比(比如数字、字符串),不能直接用于频次统计。
例如它能算:MODE() WITHIN GROUP (ORDER BY status) → 返回最常见的 status 值,但背后不是靠 COUNT,而是依赖排序后中间值逻辑,对字符串有效,对需要按频次判定的场景不适用。
所以真要按「出现次数最多」来取值,还是得走窗口函数或子查询路线,别被函数名误导。
真正容易被忽略的是:当存在并列最高频次时,不同方法行为完全不同——ROW_NUMBER() 随机选一,RANK() 全返回,子查询也全返回。业务是否允许并列,决定了你该用哪个逻辑。

















