MySQL Rangliste - Rang ermitteln

Seth93

Well-known member
ID: 322821
L
24 November 2008
1.400
82
Hallo,

ich bastel grad an einer Rangliste.
Nehmen wir an es sind 1000 Datensätze in der Datenbank.
Die Rangliste wird zur Übersicht pro Seite nur 100 Datensätze anzeigen.
Ich möchte aber dem User gleich seinen Rang zeigen. Befindet sich der User z.B. auf Platz 120, so soll automatisch die Seite 2 geladen werden, statt 1.
Gibt es eine einfache Lösung die aktuelle Platzierung zu ermitteln?
Ich hab bisher daran gedacht alle Datensätze über der Punktzahl des Users zu zählen.
Aber wenn 200 Leute dieselbe Punktzahl haben, dann ist die ermittelte Position wohlmöglich nicht korrekt.
 
Ich mache das sinngemaess so :

- Punkte des Users auslesen
- ergebnis = SELECT COUNT(*) FROM ranglistentabelle WHERE punkte < userpunkte
- Platzierung ist ergebnis+1

Haben mehrere User die geiche Punktzahl wie der betreffende User, liegen sie alle auf dem selben Platz, falls es keine sonstigen Kriterien gibt.
 
Hey,

danke für deine Antwort, aber soweit war ich auch schon.
Das Problem ist ja, dass ich dann die falsche Seitenzahl ausrechne.
Wenn ich auf Platz 120 bin, weitere 100 User den Platz ebenfalls belegen und ich aufgrund irgendwelcher Gründe auf Seite 3 bin, ermittel ich über deinen Weg Seite 2.
 
Einfach die Anzahl der Benutzer ermitteln die "davor" plaziert sind.
Allerdings muss man dann innerhalb der Benutzer eine totale Ordnung haben. Zum Beispiel zusätzlich den Benutzernamen alphabetisch sortieren.
Nun spielt es keine Rolle mehr ob mehrer Benutzer den gleichen logischen Platz einnehmen oder nicht.
 
Das ist aber sehr datenbanklastig.
Ich hab noch darüber nachgedacht die Tabelle einmal am Tag per Cron auszuwerten.
Wär das effizienter?
 
Das ist aber sehr datenbanklastig.
Ich hab noch darüber nachgedacht die Tabelle einmal am Tag per Cron auszuwerten.
Wär das effizienter?

Was bitte ist daran Datenbank-Lastig?
SELECT COUNT(*) FROM ranglistentabelle WHERE punkte < userpunkte ORDER BY userpunkt DESC, name ASC

Damit hat man eine eindeutige Sortierung und kann genau zählen, wie viele Leute davor kommen. Also hat man die Platzierung (siehe Post von Yggxx).
 
Das ist aber sehr datenbanklastig.
mit welcher Begründung?

Es muss auf eine totale Ordnung hinauslaufen, aber welche ist ja nicht festgelegt.
Du kannst genauso gut den Ranglistenwert und die Uhrzeit nehmen, dann sind es 2 simple Abfragen (Annahme: man kann nur einmal in der Rangliste stehen):
Code:
SELECT points, time FROM toplist WHERE userid = @userid;
SELECT COUNT(*) FROM toplist WHERE points > @points OR (points = @points AND time < @time);


Was bitte ist daran Datenbank-Lastig?
SELECT COUNT(*) FROM ranglistentabelle WHERE punkte < userpunkte ORDER BY userpunkt DESC, name ASC
ORDER BY zusammen mit COUNT(*)? Keine Garantie aber ich könnte mir fast sicher sein, dass das schief geht und nicht herauskommen wird was du suchst. Zumal das Ergebnis falsch für alle Datensätze ist, bei denen punkte = userpunkte ist.
 
Ja, jeder User kann nur einmal in der Tabelle stehen.

Ich hab nur bisher die Erfahrung gemacht, dass wenn ich mit mehreren ORDER BY rumgespielt habe, dass bei großen Datenmengen die Abfragen extrem lange gedauert haben.
Wenn ich für jeden User abfrage wie viele User vor ihm sind und man ganz unten ist, müssen hunderte Datensätze jedesmal gezählt werden.

Dann noch eine andere Frage:
Was ist schneller?
"LIMIT 9000, 10000" oder "WHERE platzierung >= 9000 AND platzierung <= 10000"
 
Haben die User in der Tabelle mit dem Ranglistenplatz, dort keine ID ?
Der aktuelle Platz den der User hat, wäre die ID, auf dem der User aktuell sich in der Tabelle befindet...

und wenn man eine Liste anzeigen will, wo man immer nur 100 anzeigt, dann kann man doch ne einfache Rechnung machen:

a = (string) aktuelle ID (Position)
fpos = (a[0]*100)


merke gerade, da musst noch anders ran, aber so in der art
Man kann ja vorher prüfen, ob 4 stellig oder 3 oder 5stellig, je nach maximal index...

und dann halt im select Limit(fpos,100)

so würden immer die hundert angezeigt, in denen er sich befindet
 
Das funktioniert nicht wie Seth93 schon richtig erkannt hat. Hier liegt keine Totale Ordnung vor, da es Mehrfachbelegungen eines Platzes gibt.
Mögliche Lösungsansätze liegen aber auch schon vor.

Ich würde behaupten das LIMIT schneller ist, da der Datenbankmanager? solche Schlüsselworte anders behandelt. Die Where-Bedingung wird erst nachträglich auf die Projektion der Daten ausgeführt.

Du weist aber schon das dein Beispiel nicht äquivalent ist?
 
War ja auch nur ne versinnbildlichung für einen Ansatz.
Ich versteh nur nicht, warum man Doppelbelegungen hat...
Mir ist's egal, aber wenn man sich mal was Ausdenkt, sollte man auch die Probleme bedenken, die es mit sich bringt oder ins sich birgt.
Viele haben eine Idee, und fangen an zu bauen.
Wer nur halb plant muss hinterher nicht rumjammern, weil er nicht ganz klar kommt mit den nötigen Anpassungen...
Das AundO bei Software ist die Planung, oder halt was will der Kunde wirklich von mir in seinem Produkt haben...
 
Wenn ich auf Platz 120 bin, weitere 100 User den Platz ebenfalls belegen und ich aufgrund irgendwelcher Gründe auf Seite 3 bin, ermittel ich über deinen Weg Seite 2.
Und genau da musst du doch erstmal ansetzen... Wodurch wird denn entschieden, wo der User landet, wenn andere die gleiche Punktzahl haben? Diese Sortierungs-Kriterien musst du dann natürlich auch bei der Auswahl der richtigen Seite berücksichtigen.

Wie oft ändern sich denn die Grundlagen der Rangliste? Können sich die Punkte, die zu den Platzierungen führen, ständig ändern oder nur zu bestimmten Zeiten?
Dann könnte man sich auch überlegen, die Rangliste nicht beim Abruf zu ermitteln, sondern zu dem Zeitpunkt, wo sich an den Punkten etwas ändert. Bei einem Bundesliga-Tippspiel z.B. ist es wohl performanter, bei der Auswertung der letzten Ergebnisse einmal die Liste neu zu erzeugen und die Such-Abfragen dann nur auf die fertige Liste loszulassen, statt bei jeder Ranglistenabfrage alle Plätze neu zu berechnen.

a = (string) aktuelle ID (Position)
fpos = (a[0]*100)
Du willst nicht wirklich eine String-Verarbeitung machen, um Performance aus einer Datenbank rauszukitzeln, oder?

Hier liegt keine Totale Ordnung vor, da es Mehrfachbelegungen eines Platzes gibt.
Ja, aber genau die ist hier zwingend erforderlich. Wenn du nicht ermitteln kannst, welcher der 100 120. Plätze ein bestimmter User belegt, kannst du auch nicht wissen, auf welcher Seite der liegt. Also muss zwingend ein weiteres Sortierkriterium definiert werden, das bei Punktgleichheit greift (ID, Name, Zeitpunkt der letzten Änderung,...)

Ich würde behaupten das LIMIT schneller ist, da der Datenbankmanager? solche Schlüsselworte anders behandelt. Die Where-Bedingung wird erst nachträglich auf die Projektion der Daten ausgeführt.
Das würde ich genau umgekehrt sehen. Wenn du eine Tabelle mit den Zahlen von 1 bis 100 in der Spalte a hast, und machst nun eine Abfrage

select a from tab order by a limit 30,10

dann muss die Datenbank zunächst alle a aus der Tabelle auslesen und sortieren und anschließend die ersten 30 und die letzen 60 Ergebniszeilen wegwerfen. Dagegen wird bei

select a from tab where a > 30 and a < 41 order by a

tatsächlich nur ein Ergebnisrecordset bestehend aus 10 Zeilen abgerufen wird, was dann sortiert wird. Würde mich sehr wundern, wenn die LIMIT-Variante schneller sein sollte...
 
Man kann auch alternativ arbeiten:

if (( pos % 1000 ) > 0 ) { /*zahl ist größergleich 1000*/ }

so kann man auch abfragen aber man kann auch mal dem anderen sowas überlassen herauszufinden ;)

Im Endeffekt wird bei der order Variante wenn es einen Index gibt sich darüber entlang gehangelt.
Was wäre, wenn es keinen gäbe ?

Nachtrag: Beiheutigen Kapazitäten an CPU Zyklen ist das spürbar bei einer Menge mit 100 nicht messbar, leider ...
 
if (( pos % 1000 ) > 0 ) { /*zahl ist größergleich 1000*/ }
Man kann alternativ solche Berechnungen auch direkt in der Datenbank ausführen, statt sich erst die eigene Position aus der Datenbank zu lesen, diese in PHP zu verarbeiten und anschließend wieder eine Datenbankabfrage hinterher zu schieben... Sollte das dann zu kompliziert für ein einfaches select werden, kann man sich immernoch (eine entsprechend aktuelle MySQL-Version vorausgesetzt) mit stored procedures behelfen...

Im Endeffekt wird bei der order Variante wenn es einen Index gibt sich darüber entlang gehangelt.
Was wäre, wenn es keinen gäbe ?
Eine Order-By-Klausel braucht man ja bei beiden Varianten. Ob bei dieser Sortierung ein Index einen Vorteil bringt, kann ich im Moment nicht sagen, ist aber für den Vergleich limit <-> where auch unerheblich.
Bei der Variante mit where-Klausel kommt dann aber dazu, dass mithilfe des Index ein Teil der Daten gar nicht erst eingelesen wird, um dann zu schauen, ob die Zeile nun für das Ergebnis relevant ist, oder nicht.
Der Geschwindigkeitsvorteil der where-Variante dürfte mit Index also noch ein bisschen größer sein, als ohne. So oder so sollte aber die limit-Variante diesen Wettlauf verlieren. (Auch wenn sich das bei diesem einfachen Beispiel sicherlich nicht messen lässt.)
 
Dann noch eine andere Frage:
Was ist schneller?
"LIMIT 9000, 10000" oder "WHERE platzierung >= 9000 AND platzierung <= 10000"

Das ist ganz einfach ;)
  • LIMIT 9000, 10000: Die Datenbank muss anhand deiner Bedingungen 9000 Ergebnisse suchen, diese verwerfen um daraufhin die dahinterliegenden 1000 Datensätze dir als Ergebnis liefern zu können.
  • WHERE platzierung >= 9000 AND platzierung <= 10000: Die Datenbank wird deinen Index der auf platzierung liegt nutzen, wird in dem Index schnell nachschlagen wo der erste Datensatz mit platzierung >= 9000 beginnt und wird von da an jeden Datensatz in die Ergebnismenge kopieren, solange wie platzierung <= 10000 erfüllt ist.
Für LIMIT 9000, 10000: muss die Datenbank natürlich auch ggf. ein WHERE auswerten, also hat es den Aufwand einer simplen Selektion (WHERE) und zusätzlich das sinnlose Wegwerfen von tausenden Datensätzen, die auf die Bedingung passen würden.

Welcher von beiden Ansätzen nun effizienter ist, sollte absolut ersichtlich sein. Das ganze hat Marac schonmal angerissen, ich habe es nochmal etwas deutlicher ausformuliert.