MySQL: Zufälligen Datensatz nach relativer Häufigkeit auswählen ohne RAND()

Wie viele vielleicht wissen kann man ganz einfach mit der MySQL-Funktion RAND() über ORDER BY RAND() einen zufälligen Datensatz auswählen. Dabei wird aber für JEDEN der Datensätze eine Zufallszahl ausgewürfelt, was bei sehr großen Datenmengen das Script unglaublich ausbremsen kann. Dazu habe ich mir folgende Lösung überlegt.Verdeutlichen möchte ich das an einem kleinen Beispiel. Sagen wir mal wir haben eine Seite, auf der die Benutzer Artikel veröffentlichen können. Unter diesen Benutzern wollen wir etwas verlosen. Umso mehr Artikel der Benutzer geschrieben hat, desto wahrscheinlicher soll es werden, dass er gewinnt ( relative Häufigkeit der Artikel vom Benutzer im Bezug auf die gesamt-Menge der Artikel ). Wir würfeln also im Prinzip einen gültigen Artikel aus, der eine Benutzer ID hat. Dadurch, dass wir bei jedem Artikel die Benutzer ID dazugespeichert haben ist die relative Häufigkeit automatisch mit drin.

Es mögen 10 Artikel existieren. Davon stammen 3 von Benutzer A, 3 von Benutzer B, 2 von C und 2 von D. Im Prinzip würfeln wir einmal von 1-10 und gucken welche ID wir finden. Für A und B gibt es nun logischerweise 3 Zahlen, bei denen sie gelost werden. Für B und C nur jeweils 2.

Erstmal brauchen wir also die Anzahl der gültigen Einträge. Holen wir uns die mal aus der DB:

$stmt = $db->query('SELECT
                          COUNT(*) AS count
                        FROM
                          articles
                        WHERE
                            articleIsValid = 1
                          AND
                            articleUserId != 0'
    );
    $count = $stmt->fetchAll();
    $count = $count[0]['count'];

Nun haben wir in der Variable $count die Anzahl aller Einträge, die ausgelost werden können.
( “entryUserId != 0″ beschränkt das auf die Einträge, wo auch die ID mit drin steht. Kann ja sein, dass auch unregistrierte Benutzer Artikel schreiben können, die wollen wir dann natürlich NICHT mit auswürfeln. )
Wer sich fragt was $db ist: Ich benutze die Zend_Db Klasse mit der PDO Extension von PHP. Man kann die Queries natürlich auch ganz normal ausführen.

Nun kennen wir die Anzahl der Einträge. Und was bringt uns das ? Wir können nun einen auswürfeln. Und zwar würfeln wir nur ein einziges Mal in PHP, anstatt für JEDEN Eintrag einmal:

$rand = rand(1, $count);

Wir würfeln also von 1 bis zu der Anzahl der gefundenen Artikel, die an der Verlosung teilnehmen. Nun brauchen wir uns nurnoch das Ergebnis zu holen:

$stmt = $db->query('SELECT
                          articleUserId
                        FROM
                          articles
                        WHERE
                            articleIsValid = 1
                          AND
                            articleUserId != 0
                        LIMIT ' . $rand .', 1'
    );
    $userId = $stmt->fetchAll();

Mit dem LIMIT-Statement sagen wir, wir wollen genau einen Datensatz haben, und zwar ab der Positon $rand. Heißt also nur den Datensatz an der Position $rand. Und ZACK. Schon sind wir fertig. Wir haben nach relativer Häufigkeit der Artikel eines Benutzers eine Benutzer-ID unter allen Benutzern ausgewürfelt, die einen Artikel geschrieben haben, und zwar mit nur einmal Würfeln.

Lasst mir doch gern nen Kommentar da, wenn euch der Artikel gefallen hat. Gerne auch wenn ihr ihn nicht gut findet, aber dann bitte mit konstruktiver Kritik.

  • Erek Röös

    Vieles kann so einfach sein! Danke für den Artikel. Werde es zwar nicht genauso umsetzen, aber du hast mich mit deiner Herangehensweise auf den richtigen und vorallem viel schnelleren Weg gebracht ;-) Werde das nicht mit einer ID in der DB machen, sondern mit einem Array und dem vorhanden numerischen Index.

  • Erek Röös

    Vieles kann so einfach sein! Danke für den Artikel. Werde es zwar nicht genauso umsetzen, aber du hast mich mit deiner Herangehensweise auf den richtigen und vorallem viel schnelleren Weg gebracht ;-) Werde das nicht mit einer ID in der DB machen, sondern mit einem Array und dem vorhanden numerischen Index.

  • Niemand

    Guten Tag,
    mir ist klar, dass dieser Artikel schon recht alt ist, allerdings hat sich dort ein Fehler eingeschlichen den bestimmt viele übernommen haben.
    $rand = rand(1, $count);
    Das ist Falsch, es müsste $rand = rand(0, $count-1); heißen.
    Fatal daran ist, dass es bei großen Datenbanken nicht auffällt und wenn es doch mal zu einem Fehler kommt, dann so selten, dass es niemand mehr heraus findet.