2010-04-28 11 views
8

Ich habe die folgende Tabellenstruktur für einen Tisch SpielerAktualisieren Sie den Rang in einer MySQL-Tabelle

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

Unter der Annahme, dass die playerid und die Punkte gültige Werte haben, kann ich den Rang für alle Spieler auf der Basis aktualisieren Anzahl der Punkte in einer einzelnen Abfrage? Wenn zwei Personen die gleiche Anzahl von Punkten haben, sollten sie für den Rang binden.

UPDATE:

Ich verwende Hibernate die Abfrage unter Verwendung vorgeschlagen als native Abfrage. Hibernate verwendet keine Variablen, insbesondere das ':'. Kennt jemand Workarounds? Entweder indem Sie keine Variablen verwenden oder in diesem Fall mit HQL umgehen.

+0

@samchmaly: In Bezug auf Ihre Bearbeitung im Winterschlaf, möchten Sie vielleicht eine neue Frage zu stellen, da es mehr Aufmerksamkeit bekommen wird. –

Antwort

16

Eine Option ist ein Ranking-Variable, wie die folgenden zu verwenden:

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; 

Der JOIN (SELECT @curRank := 0) Teil ermöglicht die variable Initialisierung ohne einen separaten SET Befehl erforderlich ist.

Weiterführende Literatur zu diesem Thema:


Testfall:

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; 

Ergebnis:

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) 

UPDATE: bemerkt nur die, die Sie Verbindungen erfordern den gleichen Rang zu teilen.Das ist ein bisschen schwierig, aber mit noch mehr Variablen gelöst werden:

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; 

Für einen Testfall, lassen Sie uns einen anderen Spieler mit 175 Punkten hinzu:

INSERT INTO player VALUES (6, 175, NULL); 

Ergebnis:

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) 

Und wenn Sie den Rang benötigen, um einen Platz im Falle einer Krawatte zu überspringen, können Sie eine weitere IF Bedingung hinzufügen:

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; 

Ergebnis:

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) 

Hinweis: Bitte beachten Sie, dass die Abfragen Ich schlage vor, weiter vereinfacht werden könnten.

+0

@Daniel, danke, das ist genau das, was ich brauchte. Danke für die Links. – smahesh

+0

Daniel, bitte sehen Sie meinen Kommentar zu meiner eigenen Antwort. –

3

EDIT: Die Update-Anweisung, die zuvor vorgestellt wurde, hat nicht funktioniert.

Obwohl dies nicht genau das, was Sie fordern: Sie den Rang on the fly generieren können bei der Auswahl:

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: Der Versuch, die UPDATE-Anweisung noch einmal. Wie wäre es mit einer temporären Tabelle:

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; 

Ich hoffe, dies hilft.

+0

@Tom: Nein, es wird nicht funktionieren. Sie erhalten eine 'Sie können Zieltabelle 'p1' für die Aktualisierung in FROM-Klausel nicht angeben, wegen der' p1' Referenz in der Unterabfrage. –

+0

Danke für die Klarstellung Daniel. Da Oberst Shrapnel darauf hinwies, dass streng genommen der Rang zu einem bestimmten Zeitpunkt berechnet werden sollte, möchte ich darauf hinweisen, dass mein Subselect zu diesem Zweck funktionieren sollte. –

+0

@Tom: Ja, diese Unterabfrage würde zur 'SELECT'-Zeit funktionieren, aber sie wird immer noch nicht mit Verbindungen umgehen. Das OP hat die Frage sogar als "Krawatte" markiert! :) –

0

Gemäß Normalization rules sollte der Rang zur SELECT-Zeit ausgewertet werden.

+1

Ja, aber dies ist in erster Linie eine Nachschlagetabelle, in der der Rang periodisch berechnet wird und ich nicht jedes Mal, wenn sich ein Benutzer anmeldet, ausführen möchte. – smahesh

6

Daniel, du hast eine sehr schöne Lösung. Bis auf einen Punkt - den Krawattenkoffer. Wenn eine Verbindung zwischen 3 Spielern besteht, funktioniert dieses Update nicht richtig. Ich änderte Ihre Lösung wie folgt:

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; 
Verwandte Themen