Mettre à jour le rang dans un tableau MySQL
Question
J'ai la structure de tableau ci-dessous pour une table lecteur
Table Player {
Long playerID;
Long points;
Long rank;
}
En supposant que le playerID et les points ont des valeurs valides, puis-je mettre à jour le rang pour tous les joueurs en fonction du nombre de points dans une seule requête? Si deux personnes ont le même nombre de points, ils doivent attacher le rang.
Mise à jour:
J'utilise veille prolongée à l'aide de la requête proposée comme une requête native. Mise en veille prolongée ne fonctionne pas comme l'utilisation de variables, en particulier les « : ». Est-ce que quelqu'un sait de solutions de contournement? Soit en utilisant des variables ou pas de travail autour de la limitation de mise en veille prolongée dans ce cas en utilisant HQL?
La solution
Une option consiste à utiliser une variable de classement, comme suit:
UPDATE player
JOIN (SELECT p.playerID,
@curRank := @curRank + 1 AS rank
FROM player p
JOIN (SELECT @curRank := 0) r
ORDER BY p.points DESC
) ranks ON (ranks.playerID = player.playerID)
SET player.rank = ranks.rank;
La partie JOIN (SELECT @curRank := 0)
permet à l'initialisation des variables, sans nécessiter une commande de SET
séparé.
Pour en savoir plus sur ce sujet:
cas de test:
CREATE TABLE player (
playerID int,
points int,
rank int
);
INSERT INTO player VALUES (1, 150, NULL);
INSERT INTO player VALUES (2, 100, NULL);
INSERT INTO player VALUES (3, 250, NULL);
INSERT INTO player VALUES (4, 200, NULL);
INSERT INTO player VALUES (5, 175, NULL);
UPDATE player
JOIN (SELECT p.playerID,
@curRank := @curRank + 1 AS rank
FROM player p
JOIN (SELECT @curRank := 0) r
ORDER BY p.points DESC
) ranks ON (ranks.playerID = player.playerID)
SET player.rank = ranks.rank;
Résultat:
SELECT * FROM player ORDER BY rank;
+----------+--------+------+
| playerID | points | rank |
+----------+--------+------+
| 3 | 250 | 1 |
| 4 | 200 | 2 |
| 5 | 175 | 3 |
| 1 | 150 | 4 |
| 2 | 100 | 5 |
+----------+--------+------+
5 rows in set (0.00 sec)
Mise à jour: Il suffit de remarquer le besoin que vous les liens à partager le même rang. Ceci est un peu délicat, mais peut être résolu avec des variables encore plus:
UPDATE player
JOIN (SELECT p.playerID,
IF(@lastPoint <> p.points,
@curRank := @curRank + 1,
@curRank) AS rank,
@lastPoint := p.points
FROM player p
JOIN (SELECT @curRank := 0, @lastPoint := 0) r
ORDER BY p.points DESC
) ranks ON (ranks.playerID = player.playerID)
SET player.rank = ranks.rank;
Pour un test, nous allons ajouter un autre joueur avec 175 points:
INSERT INTO player VALUES (6, 175, NULL);
Résultat:
SELECT * FROM player ORDER BY rank;
+----------+--------+------+
| playerID | points | rank |
+----------+--------+------+
| 3 | 250 | 1 |
| 4 | 200 | 2 |
| 5 | 175 | 3 |
| 6 | 175 | 3 |
| 1 | 150 | 4 |
| 2 | 100 | 5 |
+----------+--------+------+
6 rows in set (0.00 sec)
Et si vous avez besoin au rang de sauter une place en cas d'égalité, vous pouvez ajouter une autre condition IF
:
UPDATE player
JOIN (SELECT p.playerID,
IF(@lastPoint <> p.points,
@curRank := @curRank + 1,
@curRank) AS rank,
IF(@lastPoint = p.points,
@curRank := @curRank + 1,
@curRank),
@lastPoint := p.points
FROM player p
JOIN (SELECT @curRank := 0, @lastPoint := 0) r
ORDER BY p.points DESC
) ranks ON (ranks.playerID = player.playerID)
SET player.rank = ranks.rank;
Résultat:
SELECT * FROM player ORDER BY rank;
+----------+--------+------+
| playerID | points | rank |
+----------+--------+------+
| 3 | 250 | 1 |
| 4 | 200 | 2 |
| 5 | 175 | 3 |
| 6 | 175 | 3 |
| 1 | 150 | 5 |
| 2 | 100 | 6 |
+----------+--------+------+
6 rows in set (0.00 sec)
Note:. S'il vous plaît considérer que les questions que je propose pourrait simplifier encore
Autres conseils
Daniel, vous avez une solution très agréable. Sauf un point - le cas de cravate. Si égalité se passe entre 3 joueurs cette mise à jour ne fonctionne pas correctement. J'ai changé votre solution comme suit:
UPDATE player
JOIN (SELECT p.playerID,
IF(@lastPoint <> p.points,
@curRank := @curRank + @nextrank,
@curRank) AS rank,
IF(@lastPoint = p.points,
@nextrank := @nextrank + 1,
@nextrank := 1),
@lastPoint := p.points
FROM player p
JOIN (SELECT @curRank := 0, @lastPoint := 0, @nextrank := 1) r
ORDER BY p.points DESC
) ranks ON (ranks.playerID = player.playerID)
SET player.rank = ranks.rank;
EDIT:. La déclaration de mise à jour présentée précédemment n'a pas fonctionné
Bien que ce n'est pas exactement ce que vous demandez: vous pouvez générer le rang à la volée lors de la sélection:
select p1.playerID, p1.points, (1 + (
select count(playerID)
from Player p2
where p2.points > p1.points
)) as rank
from Player p1
order by points desc
EDIT: Essayer l'instruction UPDATE une fois de plus. Que diriez-vous d'une table temporaire:
create temporary table PlayerRank
as select p1.playerID, (1 + (select count(playerID)
from Player p2
where p2.points > p1.points
)) as rank
from Player p1;
update Player p set rank = (select rank from PlayerRank r
where r.playerID = p.playerID);
drop table PlayerRank;
Hope this helps.
Selon règles Normalization, doit être évaluée au moment de SELECT rang.