用GROUP BY+窗口函数找每组占比最高的记录需两步:先按组和值统计频次,再用RANK()或ROW_NUMBER()按频次降序排名并取rn=1;RANK()返回全部并列众数,ROW_NUMBER()仅返回一个且需加确定性排序字段防结果不稳定。

用 GROUP BY + 窗口函数找每组占比最高的记录
直接用 GROUP BY 无法拿到“占比最高”的那条原始记录,因为聚合会丢失明细。必须结合窗口函数先算出每组内各值的出现频次或比例,再筛选。核心是两步:先按组和维度统计,再用 ROW_NUMBER() 或 RANK() 标出“最常见”项。
常见错误是写成 SELECT ..., COUNT(*)/SUM(COUNT(*)) OVER (PARTITION BY group_col) 却忘了加 GROUP BY —— 这会导致语法错误。正确做法是先聚合,再套窗口函数,或者一步用窗口函数完成计数+排序。
- 推荐用
COUNT(*) OVER (PARTITION BY group_col, value_col)计算每个(组, 值)组合的频次 - 再用
ROW_NUMBER() OVER (PARTITION BY group_col ORDER BY COUNT(*) DESC, value_col)排序,注意加value_col防止并列时结果不稳定 - 外层过滤
rn = 1即可得到每组占比最高的那条记录(若并列,ROW_NUMBER()只取一个,RANK()会都取)
处理并列情况:RANK() vs ROW_NUMBER() 的实际影响
当某组内多个 value_col 出现次数相同时,ROW_NUMBER() 强制给唯一序号,结果只返回一条;RANK() 会给并列项相同排名,后续跳过位次——这意味着可能返回多条记录。
比如组 A 中 'X' 和 'Y' 各出现 5 次,其余为 3 次:ROW_NUMBER() 可能随机选 'X' 或 'Y'(取决于排序二级条件),而 RANK() 会让两者都带 rank = 1,都能被 WHERE rank = 1 捕获。
- 要“严格取一个”,用
ROW_NUMBER()并在ORDER BY中加入确定性字段,如ORDER BY COUNT(*) DESC, value_col ASC - 要“全量返回所有最高占比项”,用
RANK() -
DENSE_RANK()在这里没意义,它不适用于“找最大占比”这种场景
避免 COUNT(*) / SUM(COUNT(*)) 的嵌套陷阱
有人试图在单层查询里写 COUNT(*) / SUM(COUNT(*)) OVER (PARTITION BY group_col),这会报错,因为 COUNT(*) 是聚合函数,不能直接和窗口函数混用在同一层级。必须分两层:子查询或 CTE 先聚合,外层再算比例。
- 错误写法:
SELECT group_col, value_col, COUNT(*) / SUM(COUNT(*)) OVER (PARTITION BY group_col) AS ratio ... GROUP BY group_col, value_col→ 报错ERROR: aggregate function calls cannot contain window function calls - 正确写法:先 CTE 算频次,再算比例:
WITH freq AS ( SELECT group_col, value_col, COUNT(*) AS cnt FROM t GROUP BY group_col, value_col ) SELECT *, cnt::DECIMAL / SUM(cnt) OVER (PARTITION BY group_col) AS ratio FROM freq
- 比例列类型建议显式转
DECIMAL或FLOAT,避免整数除法截断(如 PostgreSQL 中5/10 = 0)
MySQL 8.0+ 和旧版兼容性要点
MySQL 5.7 不支持窗口函数,强行用会报错 FUNCTION xxx does not exist。必须确认版本,或改用自连接、相关子查询等低效替代方案。
- MySQL 8.0+ 可直接用
ROW_NUMBER() OVER (...) - MySQL 5.7 或更早:需用变量模拟,但不可靠(执行计划变化可能导致序号错乱),或用
(SELECT COUNT(*) FROM t t2 WHERE t2.group_col = t1.group_col AND t2.value_col >= t1.value_col)类方式,性能差且逻辑难维护 - SQLite 3.25+ 支持窗口函数,但不支持
FRAME子句;PostgreSQL 和 SQL Server 全功能支持
真正麻烦的不是语法怎么写,而是搞清“占比”到底指频次占比、金额占比,还是其他加权逻辑——一旦加权,COUNT(*) 就得换成 SUM(weight_col),整个计算链都要重审。

















