2010-04-28 26 views
8

Tengo la siguiente estructura de la tabla para una tabla del jugadoractualización de la fila en una tabla MySQL

Table Player { 
Long playerID; 
Long points; 
Long rank; 
} 

Suponiendo que el playerId y los puntos tienen valores válidos, puedo actualizar el rango para todos los jugadores en base a la número de puntos en una sola consulta? Si dos personas tienen el mismo número de puntos, deberían empatar para el rango.

ACTUALIZACIÓN:

estoy usando Hibernate utilizando la consulta sugirió como una consulta nativa. A Hibernate no le gusta usar variables, especialmente el ':'. ¿Alguien sabe de alguna solución? ¿O no usando variables o trabajando en torno a la limitación de hibernación en este caso utilizando HQL?

+0

@sammichy: En cuanto a su edición en hibernación, es posible que desee publicar una nueva pregunta, ya que recibirá más atención. –

Respuesta

16

Una opción es utilizar una variable de clasificación, 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:


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; 

Resultado:

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: di 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); 

Resultado:

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 necesita el rango para omitir un lugar en caso de empate, puede agregar 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; 

Resultado:

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, consideran que las consultas que estoy sugiriendo podrían simplificarse aún más.

+0

@Daniel, gracias, esto es exactamente lo que necesitaba. Gracias por los enlaces. – smahesh

+0

Daniel, por favor vea mi comentario en mi propia respuesta. –

3

EDITAR: La instrucción de actualización presentada anteriormente no funcionó.

Aunque 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.

+0

@Tom: No, no funcionará. Obtendría un 'No puede especificar la tabla de destino 'p1' para la actualización en la cláusula FROM, debido a la referencia' p1' en la subconsulta. –

+0

Gracias por la aclaración Daniel. Como el Coronel Shrapnel señaló que, estrictamente hablando, el rango debería calcularse en un momento determinado, permítanme señalar que mi subselección debería funcionar para ese propósito. –

+0

@Tom: Sí, esa subconsulta funcionaría en la hora 'SELECT', pero aún no manejará los vínculos. ¡OP incluso etiquetó la pregunta como 'empate'! :) –

0

De acuerdo con Normalization rules, el rango debe evaluarse a la hora SELECCIONAR.

+1

Sí, pero esta es principalmente una tabla de búsqueda donde el rango se calcula periódicamente y no quiero ejecutar esto cada vez que un usuario inicia sesión. – smahesh

6

Daniel, tienes una solución muy buena. Excepto un punto: el estuche de corbata. Si se produce un empate entre 3 jugadores, esta actualización no funciona correctamente. Cambié su solución de la siguiente manera:

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; 
Cuestiones relacionadas