用row_number()与count(*)组合可模拟分组众数:先group by统计频次,再用row_number()按频次降序排序取rn=1;postgresql可用mode()函数直接求众数,mysql/sql server需手动模拟,注意null处理与索引优化。

用 ROW_NUMBER() + COUNT(*) 找分组内出现最多的值
众数不是窗口函数原生支持的聚合类型,得靠“先统计频次、再按频次排序取第一”来模拟。核心思路是:对每个分组,算出每个值的出现次数,再给这些值按次数降序排,取排名第一的那个。
常见错误是直接在 OVER 里套两层聚合(比如 COUNT(*) OVER (PARTITION BY group_col, value_col)),这会报错 —— 窗口函数里不能嵌套聚合函数,除非外层是 ROW_NUMBER() 这类排序函数。
- 先用
COUNT(*)配合GROUP BY算出每个(group_col, value_col)组合的频次 - 再用
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY cnt DESC, value_col)给每组内的值按频次排序(加value_col是为防并列时结果不确定) - 最后外层筛选
rn = 1
示例(找每部门工资出现次数最多的职级):
SELECT dept, job_level
FROM (
SELECT dept, job_level,
COUNT(*) AS cnt,
ROW_NUMBER() OVER (
PARTITION BY dept
ORDER BY COUNT(*) DESC, job_level
) AS rn
FROM employees
GROUP BY dept, job_level
) t
WHERE rn = 1;
处理并列众数:用 MODE() WITHIN GROUP(PostgreSQL 专用)
PostgreSQL 9.4+ 提供了 MODE() 聚合函数,能直接返回分组内出现最频繁的值,且天然支持并列情况下的任意一个(非随机,是按排序规则选最小/最大)。但它不是窗口函数,只能配合 GROUP BY 使用。
注意:MySQL 和 SQL Server 没这个函数;SQLite 不支持;Oracle 有 STATS_MODE(),但行为略有差异(遇并列抛异常)。
-
MODE() WITHIN GROUP (ORDER BY value_col)必须带ORDER BY,否则语法错误 - 排序字段不一定要和众数值相同,但会影响并列时选哪个(例如
ORDER BY salary DESC会让高薪职级优先胜出) - 如果某组所有值频次相同,它返回排序后第一个(不是随机)
示例:
SELECT dept, MODE() WITHIN GROUP (ORDER BY job_level) FROM employees GROUP BY dept;
MySQL / SQL Server 怎么绕过没 MODE() 的限制
这两家都得手动模拟:先聚合频次,再用窗口函数取 Top 1。但 MySQL 8.0+ 和 SQL Server 2005+ 都支持 ROW_NUMBER(),写法和第一个方案一致,只是要注意子查询别名必须显式声明(MySQL 8.0 要求派生表有别名)。
容易踩的坑是忽略 NULL 值的影响 —— COUNT(*) 会把 NULL 当成一个独立值统计,而业务上往往想排除它。这时候得加 WHERE value_col IS NOT NULL。
- SQL Server 中可考虑用
TOP 1 WITH TIES配合ORDER BY COUNT(*) DESC,但需在子查询里用GROUP BY+ORDER BY,不能直接窗口化 - MySQL 5.7 不支持窗口函数,只能用自连接或变量模拟,稳定性差,不建议用于生产
- 若众数需要实时计算且数据量大,记得在
(group_col, value_col)上建联合索引
为什么不用 MAX(COUNT(*)) 或子查询 IN 匹配最高频次
看起来简洁,实际不可靠。因为 COUNT(*) 是聚合函数,不能直接嵌套在另一个聚合里(MAX(COUNT(*)) 语法非法);而用子查询找“最高频次”,再 IN 匹配,会漏掉并列情况 —— 如果两个值都出现 5 次,且 5 是最高频次,IN 会返回两个,但你通常只要一个代表值。
更隐蔽的问题是性能:子查询方式可能触发多次全表扫描,尤其当外层 GROUP BY 字段区分度低时,执行计划容易退化。
- 窗口函数方案只需一次分组扫描 + 一次排序,IO 更可控
-
MODE()是 C 语言实现的内置逻辑,比纯 SQL 模拟快不少(尤其大数据集) - 如果业务允许近似众数(比如抽样估算),反而该避免精确统计,防止拖慢报表
真正麻烦的从来不是怎么写出第一个能跑的结果,而是当某天数据里突然混进空字符串、全角空格、大小写混用的同一语义值时,众数结果就悄悄偏了 —— 这类清洗逻辑得前置,不能指望 SQL 自动归一。










