Ich habe die Lösung von Van ausprobiert, und obwohl sie funktioniert, geht es nicht schnell.
Meine Lösung
Ich löse dieses Problem, indem ich eine separate, verknüpfte Tabelle für die Gewichtung verwalte. Die Grundstruktur der Tabelle ist ähnlich wie diese:
CREATE TABLE `table1` (
`id` int(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` varchar(100),
`weight` tinyint(4) NOT NULL DEFAULT '1',
);
CREATE TABLE `table1_weight` (
`id` bigint(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`table1_id` int(11) NOT NULL
);
Wenn ich einen Datensatz in table1
mit einer Gewichtung von 3, dann erstelle ich 3 Datensätze in table1_weight
, verknüpft mit table1
über die table1_id
Feld. Was auch immer der Wert von weight
ist in table1
, so viele verknüpfte Datensätze erstelle ich in table1_weight
.
Prüfung
Bei einem Datensatz mit 976 Datensätzen in table1
mit einem Gesamtgewicht von 2031 und somit 2031 Einträgen in table1_weight
habe ich die folgenden zwei SQLs ausgeführt:
-
Eine Version von Van's Lösung
SELECT t.*
FROM table1 t
INNER JOIN
( SELECT t.id,
SUM(tt.weight) AS cum_weight
FROM table1 t
INNER JOIN table1 tt ON tt.id <= t.id
GROUP BY t.id) tc ON tc.id = t.id,
( SELECT SUM(weight) AS total_weight
FROM table1) tt,
( SELECT RAND() AS rnd) r
WHERE r.rnd * tt.total_weight <= tc.cum_weight
ORDER BY t.id ASC
LIMIT 1
-
Verknüpfung mit einer sekundären Tabelle für die Gewichtung
SELECT t.*
FROM table1 t
INNER JOIN table1_weight w
ON w.table1_id = t.id
ORDER BY RAND()
LIMIT 1
SQL 1 benötigt durchweg 0,4 Sekunden.
SQL 2 dauert zwischen 0,01 und 0,02 Sekunden.
Schlussfolgerung
Wenn die Geschwindigkeit der Auswahl eines zufälligen, gewichteten Datensatzes keine Rolle spielt, dann ist das von van vorgeschlagene SQL für eine einzige Tabelle in Ordnung und hat nicht den Overhead der Pflege einer separaten Tabelle.
Wenn, wie in meinem Fall, eine kurze Auswahlzeit entscheidend ist, dann würde ich die Methode mit zwei Tabellen empfehlen.