MySQL、PostgreSQL、SQL Server均不支持MODE()窗口函数,需用子查询+GROUP BY+ORDER BY COUNT() DESC LIMIT 1或窗口函数ROW_NUMBER() OVER(PARTITION BY...ORDER BY COUNT() DESC, field)实现分组众数。

SQL里没有MODE()函数,怎么手动算分组众数
直接说结论:MySQL、PostgreSQL、SQL Server 都不原生支持 MODE() 窗口函数(PostgreSQL 14+ 虽有 MODE() WITHIN GROUP,但仅限于聚合,不能开窗),所以得靠 COUNT() + GROUP BY + 子查询或窗口排序来硬解。
用子查询 + ORDER BY LIMIT 取每组频次最高的值(MySQL/PostgreSQL常用)
这是最直观的做法,适合中小数据量。核心思路是:先按分组和目标字段计数,再对每组内按计数倒序排,取第一条。
常见错误是漏掉外层分组逻辑,导致只返回全局众数,而不是「每组各自的众数」:
- 没用
GROUP BY做第一层聚合 → 结果只有一行 - 子查询没关联外部分组键 →
WHERE条件写死或缺失,变成全表扫描 - 没处理并列众数(多个值频次相同)→
LIMIT 1随机返回一个,不可控
示例(查每个部门 salary 出现最多的值):
SELECT dept,
(SELECT salary
FROM employees e2
WHERE e2.dept = e1.dept
GROUP BY salary
ORDER BY COUNT(*) DESC
LIMIT 1) AS mode_salary
FROM (SELECT DISTINCT dept FROM employees) e1;
用窗口函数 ROW_NUMBER() 避免子查询嵌套(推荐用于大表或需要稳定结果)
当数据量变大,或需要明确控制并列情况(比如要返回所有并列众数),窗口函数更可控。关键是用 COUNT(*) OVER (PARTITION BY ...) 算频次,再用 ROW_NUMBER() OVER (PARTITION BY ... ORDER BY COUNT(*) DESC, salary) 排序去重。
注意点:
-
ROW_NUMBER()会强制唯一排序,即使频次相同,也能靠第二排序键(如salary)稳定选一个 - 如果想保留所有并列众数,得换用
RANK()或DENSE_RANK(),再外层过滤rank = 1 - MySQL 8.0+、PostgreSQL 8.4+、SQL Server 2005+ 支持;旧版 MySQL 必须用变量模拟,极易出错
示例(取每个部门频次最高且值最小的 salary):
WITH freq AS (
SELECT dept, salary,
COUNT(*) AS cnt,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY COUNT(*) DESC, salary) AS rn
FROM employees
GROUP BY dept, salary
)
SELECT dept, salary AS mode_salary
FROM freq
WHERE rn = 1;
ORDER BY COUNT(*) DESC 的陷阱:NULL 和隐式类型转换
ORDER BY COUNT(*) DESC 本身没问题,但实际执行时容易被忽略两点:
-
NULL值参与GROUP BY会被单独分组,若业务上不希望它成为众数,得加WHERE salary IS NOT NULL - 字符型字段(如
status VARCHAR(20))若存在大小写混用或前后空格,'active'和'Active '会被视为不同值,需提前TRIM(UPPER(status)) - 某些方言(如旧版 SQLite)不支持在子查询中直接
ORDER BY+LIMIT,必须包一层SELECT *
真正难的不是语法,是确认「你定义的众数是否符合业务语义」——比如销售区域里出现最多的产品类别,是否该排除测试数据?是否要合并别名?这些清洗步骤往往比 SQL 写法更耗时。

















