mysql 5.7+ 默认启用 only_full_group_by,导致 group by + order by + limit 1 取每组众数时报错;正确做法是用窗口函数(8.0+)或两层聚合(5.7兼容),确保每组内按频次排序后取首条,避免非确定性结果。

MySQL中用GROUP BY + ORDER BY + LIMIT 1取众数会出错
直接在子查询里对每组 COUNT(*) 排序后 LIMIT 1,MySQL 5.7+ 默认会报错:「This is incompatible with sql_mode=only_full_group_by」。因为 SELECT 列里有非分组字段(比如你要取的众数值),但没出现在 GROUP BY 中,MySQL 拒绝执行。
常见错误写法:
SELECT value FROM t GROUP BY group_id ORDER BY COUNT(*) DESC LIMIT 1——这其实只返回整个表的全局众数,不是「每组的众数」。
- 真正要的是每个
group_id对应一个最频繁出现的value - 必须先按
group_id和value两级分组,再对频次排序 -
LIMIT 1必须作用在每个组内,不能放在外层一锅端
用窗口函数ROW_NUMBER()配合COUNT(*)实现每组众数(MySQL 8.0+/PostgreSQL)
核心思路:先算出每组内每个值的出现次数,再按组内频次降序编号,取编号为 1 的记录。这样既避开 ONLY_FULL_GROUP_BY 限制,又能保证每组一条结果。
示例(假设表 sales 有字段 region、product):
WITH freq AS (
SELECT region, product, COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY COUNT(*) DESC, product) AS rn
FROM sales
GROUP BY region, product
)
SELECT region, product AS mode_product
FROM freq
WHERE rn = 1;
-
PARTITION BY region确保编号在每个地区内独立进行 -
ORDER BY COUNT(*) DESC, product解决频次相同时的不确定性(加product是为了结果可重现) - 如果某组多个
product频次相同且都是最高,ROW_NUMBER()只取其中一个;要用全部,改用RANK()并配合去重逻辑
兼容 MySQL 5.7 的写法:用关联子查询找每组最大频次
老版本不支持窗口函数,就得靠两层聚合:先算出每组每个值的频次,再对每组查出最大频次,最后关联回来取对应值。
示例:
SELECT t1.region, t1.product
FROM (
SELECT region, product, COUNT(*) AS cnt
FROM sales
GROUP BY region, product
) t1
INNER JOIN (
SELECT region, MAX(cnt) AS max_cnt
FROM (
SELECT region, product, COUNT(*) AS cnt
FROM sales
GROUP BY region, product
) t2
GROUP BY region
) t3 ON t1.region = t3.region AND t1.cnt = t3.max_cnt;
- 性能比窗口函数差,因为子查询要扫两次数据
- 如果某组有多个
product并列最高频次,这条语句会返回全部——这是行为差异,不是 bug - 注意:若只需任选其一,可在外层加
GROUP BY region并用MIN(product)或MAX(product)收口
PostgreSQL里用MODE()聚合函数最省事
PostgreSQL 内置了 MODE(),是标准 SQL-2016 定义的众数聚合函数,直接可用:
SELECT region, MODE() WITHIN GROUP (ORDER BY product) FROM sales GROUP BY region;
- 无需手动计数、排序、取顶,语义清晰
- 当存在多个众数时,
MODE()报错:「more than one row returned by a subquery used as an expression」——它要求严格唯一众数 - 如需容忍并列,得退回到窗口函数方案










