众数必须返回所有出现次数最多的值,不能用GROUP BY+LIMIT 1;应使用RANK()窗口函数或子查询先求最大频次再筛选,注意NULL是否参与统计。

众数不能直接用 GROUP BY + LIMIT 1 拿到正确结果
因为 GROUP BY 后的 ORDER BY ... LIMIT 1 只是在分组聚合后的结果集里取第一条,但若存在多个值频次相同(并列最高),它只会随机返回一个,不满足众数定义(出现次数最多的值,可能有多个)。更关键的是:如果表里有 NULL,默认会被忽略分组,但有些场景下你得明确决定是否把 NULL 当作一个候选值。
- 先用
COUNT(*)统计每个值的出现次数 - 再用窗口函数
RANK()或DENSE_RANK()标出最高频次的排名(推荐RANK(),方便处理并列) - 最后筛选出排名为
1的所有值
PostgreSQL / MySQL 8.0+:用 RANK() 窗口函数找全部众数
这是目前最可靠、语义清晰的做法。注意:MySQL 5.7 不支持窗口函数,必须升级或换思路;PostgreSQL 全版本支持。
SELECT value
FROM (
SELECT value, COUNT(*) AS cnt,
RANK() OVER (ORDER BY COUNT(*) DESC) AS rn
FROM your_table
WHERE value IS NOT NULL -- 显式控制 NULL 是否参与
GROUP BY value
) t
WHERE rn = 1;-
WHERE value IS NOT NULL这行要按需保留或删掉——如果你的业务中NULL是有效取值(比如“未填写”本身是一种状态),就得去掉该条件,并确保GROUP BY能正常分组NULL -
RANK()和DENSE_RANK()在并列时行为一致(都给相同频次同排名),但RANK()跳跃后续名次,对“取 top 1”没影响 - 别用
ROW_NUMBER()——它强制唯一编号,会漏掉并列众数
MySQL 5.7 或 SQLite:用子查询模拟窗口逻辑
没有窗口函数时,得先算出最大频次,再反查哪些值达到该频次。性能稍差,但兼容性好。
SELECT value
FROM your_table
GROUP BY value
HAVING COUNT(*) = (
SELECT MAX(cnt)
FROM (
SELECT COUNT(*) AS cnt
FROM your_table
GROUP BY value
) t
);- 这个写法在 SQLite 中可用,但注意 SQLite 的
GROUP BY对NULL的处理和 MySQL 不完全一致——测试时务必用真实数据验证 - 子查询里没加
WHERE value IS NOT NULL,所以外层结果也可能含NULL;如需排除,两处都要补上 - 如果表很大,嵌套聚合可能变慢;可考虑加
value字段的索引(尤其当它是字符串且长度可控时)
LIMIT 1 配合 ORDER BY COUNT(*) DESC 的典型翻车场景
很多人写成这样:
SELECT value FROM your_table GROUP BY value ORDER BY COUNT(*) DESC LIMIT 1;
看着简洁,但错在三个地方:
- 遇到两个值都出现 100 次,它只返回其中一个,丢失另一个众数
- MySQL 5.7 在
sql_mode=only_full_group_by下会报错:Expression #1 of SELECT list is not in GROUP BY clause,因为value没被聚合也没在GROUP BY里(虽然语义上合理,但语法不许) - PostgreSQL 完全不允许这种写法,直接报错:
column "your_table.value" must appear in the GROUP BY clause or be used in an aggregate function
真正难的不是写出来,是意识到「众数可能不止一个」这件事本身就会打破很多直觉写法。只要需求里没明说“任取一个”,就别碰 LIMIT 1。

















