按类别分月统计玩家排名时@rownumber跨月递增问题求解
这个问题我之前帮不少开发者解决过——核心痛点就是要让排名在每个「月份+统计维度」的分组里独立重置,而不是全局累加。分两种场景给你最实用的方案:
方案1:MySQL 8.0+ 用窗口函数(推荐)
如果你的MySQL版本是8.0及以上,直接用ROW_NUMBER()窗口函数就能完美解决,这也是最简洁易维护的写法。通过PARTITION BY指定分组维度(月份),让每个月的排名单独计算,自动重置为1。
假设你的Player表是按单条记录存储玩家的对局数据,我们先聚合出每个玩家每月的击杀/金币总和,再分月排名:
WITH monthly_player_stats AS ( SELECT player_id, DATE_FORMAT(stat_date, '%Y-%m') AS year_month, SUM(kills) AS total_kills, SUM(gold) AS total_gold FROM Player GROUP BY player_id, DATE_FORMAT(stat_date, '%Y-%m') ) SELECT player_id, year_month, -- 按击杀数降序排名,每月独立重置 ROW_NUMBER() OVER (PARTITION BY year_month ORDER BY total_kills DESC) AS kills_rank, -- 按金币数降序排名,每月独立重置 ROW_NUMBER() OVER (PARTITION BY year_month ORDER BY total_gold DESC) AS gold_rank FROM monthly_player_stats ORDER BY year_month, kills_rank;
如果需要支持并列排名(比如击杀数相同的玩家排名一致),可以把ROW_NUMBER()换成RANK()(并列后跳过后续排名)或DENSE_RANK()(并列后不跳过排名),根据你的业务需求调整。
方案2:MySQL 5.x 版本(无窗口函数)用变量手动重置
要是还在用不支持窗口函数的老版本MySQL,就用用户变量来手动判断分组变化,重置排名。核心思路是:先把数据按月份排序,再通过变量记录上一个月份,每次遇到新月份就把排名重置为1。
同样先聚合月度数据,再处理排名:
SELECT player_id, year_month, total_kills, total_gold, -- 处理击杀数排名:同月份累加,新月份重置为1 @kills_rank := IF(@prev_month_k = year_month, @kills_rank + 1, 1) AS kills_rank, @prev_month_k := year_month, -- 处理金币数排名:逻辑同上 @gold_rank := IF(@prev_month_g = year_month, @gold_rank + 1, 1) AS gold_rank, @prev_month_g := year_month FROM ( -- 先聚合并按月份+排名字段排序,确保同月份数据连续 SELECT player_id, DATE_FORMAT(stat_date, '%Y-%m') AS year_month, SUM(kills) AS total_kills, SUM(gold) AS total_gold FROM Player GROUP BY player_id, DATE_FORMAT(stat_date, '%Y-%m') ORDER BY year_month, total_kills DESC ) AS sorted_stats -- 初始化变量 CROSS JOIN (SELECT @kills_rank := 0, @prev_month_k := '', @gold_rank := 0, @prev_month_g := '') AS init_vars ORDER BY year_month, kills_rank;
注意:MySQL会按SELECT语句的顺序执行变量赋值,所以一定要先判断当前月份和上一个月份是否一致,再更新记录上一个月份的变量,否则会出现逻辑错误。
内容的提问来源于stack exchange,提问作者Leonardo
相关产品推荐
相关产品推荐

