Actualizar la fila en una tabla de MySQL
Pregunta
Tengo la siguiente estructura de la tabla para una tabla jugador
Table Player {
Long playerID;
Long points;
Long rank;
}
Si se asume que el playerId y los puntos tienen valores válidos, puedo actualizar el rango para todos los jugadores basándose en el número de puntos en una sola consulta? Si dos personas tienen el mismo número de puntos, se debe atar para el rango.
ACTUALIZACIÓN:
Estoy usando Hibernate utilizando la consulta sugerido como una consulta nativa. Hibernate no le gusta el uso de variables, especialmente el ':'. ¿Alguien sabe de alguna solución? Ya sea por no utilizar las variables o trabajar cerca de la limitación de hibernación en este caso mediante el uso de HQL?
Solución
Una opción es utilizar una variable de ranking, tales como los siguientes:
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 parte JOIN (SELECT @curRank := 0)
permite que la variable de inicialización sin necesidad de un mando separado SET
.
Para leer más sobre este tema:
- SQL: Clasificación y sin auto unirse a
- desbordamiento de pila: crear una columna de Suma acumulativa en MySQL
caso de prueba:
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;
Resultados:
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)
ACTUALIZACIÓN: Sólo se dio cuenta de la que necesita lazos de compartir el mismo rango. Esto es un poco complicado, pero se puede solucionar con aún más variables:
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;
Para un caso de prueba, vamos a añadir otro jugador con 175 puntos:
INSERT INTO player VALUES (6, 175, NULL);
Resultados:
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)
Y si usted requiere el rango de saltar un lugar en caso de empate, se puede añadir otra condición 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;
Resultados:
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)
Nota:. Por favor, considere que las consultas que estoy sugiriendo podrían simplificarse aún más
Otros consejos
Daniel, que tiene muy buena solución. Excepto un punto - el caso lazo. Si sucede lazo entre 3 jugadores esta actualización no funciona correctamente. He cambiado su solución como sigue:
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 instrucción de actualización presentada anteriormente no funcionó
A pesar de que esto no es exactamente lo que está pidiendo: Puede generar el rango sobre la marcha cuando la selección:
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: Tratar la instrucción UPDATE una vez más. ¿Qué tal una tabla temporal:
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;
Espero que esto ayude.
Según Normalización gobierna , el rango debe ser evaluada en el tiempo de SELECT.