MySQL:通过数据

时间:2016-08-03 11:25:24

标签: mysql

假设我有一个玩家表,每个玩家都有一个分数,现在我想根据他们的分数将所有玩家分成相同大小的等级,所以如果我有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/102*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中不可用。

注意:PlayersPlayerID(主键)和Score上都有索引,但在Score上没有索引的情况下运行似乎没有区别。我按PlayerID排序的原因是因为我在关系的情况下有明确定义的(一致的)行为。

2 个答案:

答案 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

http://sqlfiddle.com/#!9/7f55f9/3

答案 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个