MySQL 使用大型数据集更新玩家每周排名位置的查询
MySQL query to update players weekly ranking positions with large dataset
我正在尝试更新玩家的每周排名分数,但我尝试过的任何查询都会超时。 table 中大约有 10 万行。我的 table players_weekly_rankings 看起来像这样:
player_id | ranking_points | yearweek | ranking_pos
22 | 1676 | 2020/01 | 1
12 | 1620 | 2020/01 | 2
45 | 1620 | 2020/01 | 2
53 | 1544 | 2020/01 | 4
25 | 1644 | 2020/02 | 1
21 | 1555 | 2020/02 | 2
etc.
因此 ranking_pos 列是正在更新的列。
以及永远不会完成并运行到超时的查询:
update players_weekly_ranking
set ranking_pos = (
select count(distinct ranking_points) + 1
from (SELECT ranking_points, yearweek FROM players_weekly_ranking) w2
where w2.yearweek = players_weekly_ranking.yearweek and w2.ranking_points > players_weekly_ranking.ranking_points
)
和下面要求的 EXPLAIN(这个测试 tebale 只有 2000 条记录,但实际 table 接近 100k)
最多两千行,它会在两分钟内完成,但超过这个时间就会超时。
是否有更优化的方法来执行此操作,这样查询就不会 运行 超时?谢谢!
我相信这应该一次完成(没有子查询)
SET @rank = 0;
UPDATE players_weekly_ranking
SET ranking_pos = (@rank := @rank+1)
WHERE yearweek = '2020/01'
ORDER BY ranking_points DESC;
它定义了一个从 0 开始的变量 @rank
,然后通过降序 ranking_points
遍历特定 yearweek
的所有行并分配它们 ranking_pos
。 (@rank := @rank+1)
是否在每一行递增变量。
编辑:我假设你只需要更新特定一周的排名,因为过去的分数不应该改变
Edit2:这里是一个兼顾等分的版本,可以更新几个yearweeks:
SET @rank = 0; -- rank of the previous row
SET @yearweek = ''; -- yearweek of the previous row
SET @last_score = 0; -- score of the previous row
SET @nb_same_score = 0; -- number of rows "in a row" with same score
UPDATE players_weekly_ranking
SET ranking_pos = IF(
@yearweek != (@yearweek := yearweek),
IF( -- if it's a new yearweek
(@last_score := ranking_points) AND (@nb_same_score:=1),
(@rank := 1), -- first row always gets ranking_pos = 1
0
),
IF( -- if same yearweek
@last_score = (@last_score := ranking_points) AND (@nb_same_score := @nb_same_score + 1),
@rank, -- if same score as last row => set same ranking_pos
@rank := @rank + @nb_same_score + (@nb_same_score := 1) -1
)
)
ORDER BY yearweek, ranking_points DESC;
迭代每一行,按年周和点排序,这会执行以下操作:
- 如果是新的一周,第一行(最高分)的排名始终为 1。(
@yearweek
取新一周的值,@last_score
& @nb_same_score
已重置)
- 如果它与最后一行是同一周,
@last_score
将与该行的得分进行比较(并更新)。如果它们相等,则 @nb_same_score
递增
- 如果相等,该行与前一行的排名相同
- 否则它的排名会增加
@nb_same_score
。 ((@nb_same_score := 1) -1
只是为了将 @nb_same_score
变量重置为 1)
- 用 JOIN
替换 sub-query
- 在 JOIN 中 table 使用 window-function RANK() 计算排名。它会完全满足您的需求。
RANK() 仅适用于 MySQL 8+ https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_rank
如果您有 MySQL 5.* 寻找没有 RANK() 函数的解决方法:Rank function in MySQL
我正在尝试更新玩家的每周排名分数,但我尝试过的任何查询都会超时。 table 中大约有 10 万行。我的 table players_weekly_rankings 看起来像这样:
player_id | ranking_points | yearweek | ranking_pos
22 | 1676 | 2020/01 | 1
12 | 1620 | 2020/01 | 2
45 | 1620 | 2020/01 | 2
53 | 1544 | 2020/01 | 4
25 | 1644 | 2020/02 | 1
21 | 1555 | 2020/02 | 2
etc.
因此 ranking_pos 列是正在更新的列。
以及永远不会完成并运行到超时的查询:
update players_weekly_ranking
set ranking_pos = (
select count(distinct ranking_points) + 1
from (SELECT ranking_points, yearweek FROM players_weekly_ranking) w2
where w2.yearweek = players_weekly_ranking.yearweek and w2.ranking_points > players_weekly_ranking.ranking_points
)
和下面要求的 EXPLAIN(这个测试 tebale 只有 2000 条记录,但实际 table 接近 100k)
最多两千行,它会在两分钟内完成,但超过这个时间就会超时。
是否有更优化的方法来执行此操作,这样查询就不会 运行 超时?谢谢!
我相信这应该一次完成(没有子查询)
SET @rank = 0;
UPDATE players_weekly_ranking
SET ranking_pos = (@rank := @rank+1)
WHERE yearweek = '2020/01'
ORDER BY ranking_points DESC;
它定义了一个从 0 开始的变量 @rank
,然后通过降序 ranking_points
遍历特定 yearweek
的所有行并分配它们 ranking_pos
。 (@rank := @rank+1)
是否在每一行递增变量。
编辑:我假设你只需要更新特定一周的排名,因为过去的分数不应该改变
Edit2:这里是一个兼顾等分的版本,可以更新几个yearweeks:
SET @rank = 0; -- rank of the previous row
SET @yearweek = ''; -- yearweek of the previous row
SET @last_score = 0; -- score of the previous row
SET @nb_same_score = 0; -- number of rows "in a row" with same score
UPDATE players_weekly_ranking
SET ranking_pos = IF(
@yearweek != (@yearweek := yearweek),
IF( -- if it's a new yearweek
(@last_score := ranking_points) AND (@nb_same_score:=1),
(@rank := 1), -- first row always gets ranking_pos = 1
0
),
IF( -- if same yearweek
@last_score = (@last_score := ranking_points) AND (@nb_same_score := @nb_same_score + 1),
@rank, -- if same score as last row => set same ranking_pos
@rank := @rank + @nb_same_score + (@nb_same_score := 1) -1
)
)
ORDER BY yearweek, ranking_points DESC;
迭代每一行,按年周和点排序,这会执行以下操作:
- 如果是新的一周,第一行(最高分)的排名始终为 1。(
@yearweek
取新一周的值,@last_score
&@nb_same_score
已重置) - 如果它与最后一行是同一周,
@last_score
将与该行的得分进行比较(并更新)。如果它们相等,则@nb_same_score
递增- 如果相等,该行与前一行的排名相同
- 否则它的排名会增加
@nb_same_score
。 ((@nb_same_score := 1) -1
只是为了将@nb_same_score
变量重置为 1)
- 用 JOIN 替换 sub-query
- 在 JOIN 中 table 使用 window-function RANK() 计算排名。它会完全满足您的需求。
RANK() 仅适用于 MySQL 8+ https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html#function_rank
如果您有 MySQL 5.* 寻找没有 RANK() 函数的解决方法:Rank function in MySQL