假设我有一个玩家表,每个玩家都有一个分数,现在我想根据他们的分数将所有玩家分成相同大小的等级,所以如果我有n
玩家,等级1将拥有得分最高的第一个n/10
玩家,等级2将拥有下一个n/10
,依此类推。
我提出了一个问题:
UPDATE Players SET Level=? WHERE PlayerID IN (
SELECT * FROM (
SELECT PlayerID FROM Players ORDER BY Score DESC, PlayerID ASC LIMIT ?,?
) AS T1
);
我运行10次,第一个参数从1-10开始,第二个参数为0,n/10
,2*n/10
,......第三个参数始终为n/10
。
这有效,但需要很长时间。为了获得更好的结果,我创建了一个临时表:
CREATE TEMPORARY TABLE TempTable (
IDX INT UNSIGNED NOT NULL AUTO_INCREMENT,
ID INT UNSIGNED NOT NULL,
PRIMARY KEY (IDX)
) ENGINE=MEMORY;
INSERT INTO TempTable (ID) SELECT PlayerID FROM Players ORDER BY Score DESC, PlayerID ASC;
然后我跑了十次:
UPDATE Players SET Level=? WHERE PlayerID IN (
SELECT * FROM TempTable WHERE IDX BETWEEN ? AND ?
);
使用适当的参数,最后:
DROP TABLE TempTable;
但是,这会运行甚至更慢。那么在MySQL中有更有效的方法吗?我找到了this answer,但似乎NTILE
在MySQL中不可用。
注意:Players
在PlayerID
(主键)和Score
上都有索引,但在Score
上没有索引的情况下运行似乎没有区别。我按PlayerID
排序的原因是因为我在关系的情况下有明确定义的(一致的)行为。
答案 0 :(得分:2)
您可以尝试使用排名功能。这就是我使用的:
SELECT PlayerID,
score,
@levelLimit,
@counter := @counter + 1 AS counter,
@level := IF(@counter % @levelLimit = 0, @level:= @level + 1, @level) as level
FROM Players,
(SELECT @counter := 0) a,
(SELECT @levelLimit := round(count(*)/4 -- number of groups you want to end with
, 0)
FROM Players) b,
(SELECT @level := 1) c
ORDER BY Score DESC,
PlayerID ASC
;
更新表格:
UPDATE Players join (
SELECT PlayerID,
score,
@levelLimit, @counter := @counter + 1 AS counter,
@level := IF(@counter % @levelLimit = 0, @level:= @level + 1, @level) AS level
FROM Players,
(SELECT @counter := 0) a,
(SELECT @levelLimit := round(count(*)/4 -- number of clusters
, 0)
FROM Players) b,
(SELECT @level := 1) c
ORDER BY Score DESC,
PlayerID ASC
) as a on a.PlayerID = Players.PlayerID
SET Players.level = a.level
答案 1 :(得分:1)
您的查询速度慢的原因是因为最后的这个限制位:
SELECT PlayerID FROM Players ORDER BY Score DESC, PlayerID ASC LIMIT ?,?
如果没有offset, limit
,您将以十个步骤进行表扫描。使用offset,limit
您正在进行多次!基本上要获得偏移量,必须对整个数据集进行排序,然后只能将mysql移动到感兴趣的数据。我的建议是通过根据分数将字段分成几个级别来避免限制条款。
例如,如果您有10个级别,则可以执行简单查询以获取
SELECT max(score), min(score) from ...
然后根据最高和最低分的差异将字段分成10等于等级。如果像堆栈溢出一样,你有数百万用户得分为1,而不是min,你可以选择最低边界的任意数字。
然后
UPDATE Players SET Level=? WHERE PlayerID IN (
SELECT * FROM (
SELECT PlayerID FROM score < level_upper_bound and score > leve_lower bound ) AS T1
);
您仍然会以10个步骤进行表扫描,但现在只有一个表扫描而不是10个