Wie kann diese Lösung auf einen Join erweitert werden? Bei der Verwendung SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;bekomme ich immer die gleiche Zeile.
Helmut Grohne
Ist es möglich, die Zufallszahl zu setzen. zB Buch des Tages, das für heute Mittag mit Unix-Epoc erstellt wurde, sodass das gleiche Buch den ganzen Tag angezeigt wird, auch wenn die Abfrage mehrmals ausgeführt wird. Ja, ich weiß, dass das Caching für diesen Anwendungsfall effizienter ist, nur ein Beispiel.
danielson317
FWIW meine Frage wird hier tatsächlich beantwortet. Und die Antwort ist, dass Sie die Zufallszahl nicht setzen können. stackoverflow.com/questions/24256258/…
danielson317
31
Die folgenden Lösungen sind viel schneller als die von anktastic (die Anzahl (*) kostet viel, aber wenn Sie sie zwischenspeichern können, sollte der Unterschied nicht so groß sein), was selbst viel schneller ist als die "order by random ()" wenn Sie eine große Anzahl von Zeilen haben, obwohl sie einige Unannehmlichkeiten haben.
Wenn Ihre Rowids ziemlich voll sind (dh nur wenige Löschungen), können Sie Folgendes tun (verwenden Sie (select max(rowid) from foo)+1anstelle von, um max(rowid)+1eine bessere Leistung zu erzielen, wie in den Kommentaren erläutert):
select*from foo where rowid =(abs(random())%(select(select max(rowid)from foo)+1));
Wenn Sie Löcher haben, versuchen Sie manchmal, eine nicht vorhandene Zeilen-ID auszuwählen, und die Auswahl gibt eine leere Ergebnismenge zurück. Wenn dies nicht akzeptabel ist, können Sie einen Standardwert wie den folgenden angeben:
Diese zweite Lösung ist nicht perfekt: Die Wahrscheinlichkeitsverteilung ist in der letzten Zeile (der mit der höchsten Zeilen-ID) höher. Wenn Sie jedoch häufig Daten zur Tabelle hinzufügen, wird dies zu einem sich bewegenden Ziel, und die Verteilung der Wahrscheinlichkeiten sollte höher sein viel besser.
Eine weitere Lösung: Wenn Sie häufig zufällige Elemente aus einer Tabelle mit vielen Löchern auswählen, möchten Sie möglicherweise eine Tabelle erstellen, die die Zeilen der ursprünglichen Tabelle enthält, die in zufälliger Reihenfolge sortiert sind:
createtable random_foo(foo_id);
Füllen Sie dann regelmäßig die Tabelle random_foo neu aus
deletefrom random_foo;insertinto random_foo select id from foo;
Und um eine zufällige Zeile auszuwählen, können Sie meine erste Methode verwenden (hier gibt es keine Löcher). Natürlich hat diese letzte Methode einige Parallelitätsprobleme, aber die Neuerstellung von random_foo ist eine Wartungsoperation, die wahrscheinlich nicht sehr häufig vorkommt.
Eine weitere Möglichkeit, die ich kürzlich auf einer Mailingliste gefunden habe , besteht darin, beim Löschen einen Auslöser zu setzen, um die Zeile mit der größten Zeilen-ID in die aktuell gelöschte Zeile zu verschieben, sodass keine Löcher mehr vorhanden sind.
Beachten Sie zum Schluss, dass das Verhalten der automatischen Inkrementierung von Zeilen-ID und ganzzahligem Primärschlüssel nicht identisch ist (bei Zeilen-ID wird beim Einfügen einer neuen Zeile max (Zeilen-ID) +1 ausgewählt, wobei es der höchste Wert ist, der jemals gesehen wurde + 1 für ein Primärschlüssel), sodass die letzte Lösung nicht mit einer automatischen Inkrementierung in random_foo funktioniert, die anderen Methoden jedoch.
Wie ich gerade auf einer Mailingliste gesehen habe, können Sie anstelle der Fallback-Methode (Methode 2) einfach rowid> = [random] anstelle von = verwenden, aber es ist im Vergleich zu Methode 2 tatsächlich schleppend langsam.
Suzanne Dupéron
3
Dies ist eine großartige Antwort. es hat jedoch ein Problem. SELECT max(rowid) + 1wird eine langsame Abfrage sein - es erfordert einen vollständigen Tabellenscan. SQLite optimiert nur die Abfrage SELECT max(rowid). Daher würde diese Antwort verbessert werden durch: select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)); Weitere Informationen finden Sie hier: sqlite.1065341.n5.nabble.com/…
dasl
18
Sie müssen "order by RANDOM ()" in Ihre Abfrage aufnehmen.
Beispiel:
select*from quest orderby RANDOM();
Sehen wir uns ein vollständiges Beispiel an
Erstellen Sie eine Tabelle:
CREATETABLE quest (
id INTEGER PRIMARYKEY AUTOINCREMENT,
quest TEXT NOTNULL,
resp_id INTEGER NOTNULL);
Obwohl Antworten nur mit Code nicht verboten sind, verstehen Sie bitte, dass dies eher eine Q & A-Community als eine Crowd-Sourcing-Community ist und dass er / sie normalerweise aufgetaucht wäre, wenn das OP den Code verstanden hätte, der als Antwort veröffentlicht wird mit einer ähnlichen Lösung für sich allein und hätte überhaupt keine Frage gestellt. Geben Sie daher bitte einen Kontext zu Ihrer Antwort und / oder Ihrem Code an, indem Sie erläutern, wie und / oder warum dies funktioniert.
XenoRo
2
Ich bevorzuge diese Lösung, da ich damit nach n Zeilen suchen kann. In meinem Fall brauchte ich 100 Zufallsstichproben aus der Datenbank - ORDER BY RANDOM () in Kombination mit LIMIT 100 macht genau das.
mnr
17
Wie wäre es mit:
SELECT COUNT(*)AS n FROM foo;
Wählen Sie dann eine Zufallszahl m in [0, n) und
SELECT*FROM foo LIMIT 1 OFFSET m;
Sie können die erste Nummer ( n ) sogar irgendwo speichern und nur aktualisieren, wenn sich die Datenbankanzahl ändert. Auf diese Weise müssen Sie nicht jedes Mal SELECT COUNT ausführen.
Das ist eine schöne schnelle Methode. Es lässt sich nicht sehr gut verallgemeinern, mehr als eine Zeile auszuwählen, aber das OP hat nur nach 1 gefragt, also denke ich, dass das in Ordnung ist.
Ken Williams
Eine merkwürdige Sache ist, dass die Zeit, die benötigt wird, um das zu finden OFFSET, abhängig von der Größe des Versatzes zu steigen scheint - Zeile 2 ist schnell, Zeile 2 Millionen dauert eine Weile, selbst wenn alle Daten in der festen Größe sind und es sollte in der Lage sein, direkt danach zu suchen. Zumindest sieht es in SQLite 3.7.13 so aus.
Ken Williams
@ KenWilliams Nahezu alle Datenbanken haben das gleiche Problem mit "OFFSET". Es ist eine sehr ineffiziente Methode, eine Datenbank abzufragen, da sie so viele Zeilen lesen muss, obwohl sie nur 1
Jonathan Allen
1
Beachten Sie, dass ich über / feste Größe / Datensätze gesprochen habe - es sollte einfach sein, direkt auf das richtige Byte in den Daten zu scannen ( nicht so viele Zeilen zu lesen), aber sie müssten die Optimierung explizit implementieren.
Ken Williams
@ KenWilliams: In SQLite gibt es keine Datensätze mit fester Größe, sie werden dynamisch typisiert und die Daten müssen nicht mit den deklarierten Affinitäten übereinstimmen ( sqlite.org/fileformat2.html#section_2_1 ). Alles ist auf B-Tree-Seiten gespeichert, so dass in beiden Fällen mindestens eine B-Tree-Suche in Richtung des Blattes durchgeführt werden muss. Um dies effizient zu erreichen, müsste die Größe des Teilbaums zusammen mit jedem untergeordneten Zeiger gespeichert werden. Es wäre zu viel Aufwand für wenig Nutzen, da Sie das OFFSET immer noch nicht für Verknüpfungen, Bestellungen nach usw. optimieren können (und ohne ORDER BY ist die Bestellung undefiniert.)
Diese Lösung funktioniert auch für Indizes mit Lücken, da wir einen Offset in einem Bereich [0, count] randomisieren. MAXwird verwendet, um einen Fall mit leerer Tabelle zu behandeln.
Hier sind einfache Testergebnisse für eine Tabelle mit 16.000 Zeilen:
sqlite>.timer on
sqlite>select count(*)from payment;16049
Run Time: real 0.000user0.000140 sys 0.000117
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);14746
Run Time: real 0.002user0.000899 sys 0.000132
sqlite>select payment_id from payment limit 1 offset abs(random())%(select count(*)from payment);12486
Run Time: real 0.001user0.000952 sys 0.000103
sqlite>select payment_id from payment orderby random() limit 1;3134
Run Time: real 0.015user0.014022 sys 0.000309
sqlite>select payment_id from payment orderby random() limit 1;9407
Run Time: real 0.018user0.013757 sys 0.000208
Guter Versuch, aber ich denke nicht, dass dies funktionieren wird. Was ist, wenn eine Zeile mit rowId = 5 gelöscht wurde, die rowIds 1,2,3,4,6,7,8,9,10 jedoch noch vorhanden sind? Wenn dann die ausgewählte zufällige Zeilen-ID 5 ist, gibt diese Abfrage nichts zurück.
Antworten:
Schauen Sie sich das Auswählen einer zufälligen Zeile aus einer SQLite-Tabelle an
quelle
SELECT a.foo FROM a JOIN b ON a.id = b.id WHERE b.bar = 2 ORDER BY RANDOM() LIMIT 1;
bekomme ich immer die gleiche Zeile.Die folgenden Lösungen sind viel schneller als die von anktastic (die Anzahl (*) kostet viel, aber wenn Sie sie zwischenspeichern können, sollte der Unterschied nicht so groß sein), was selbst viel schneller ist als die "order by random ()" wenn Sie eine große Anzahl von Zeilen haben, obwohl sie einige Unannehmlichkeiten haben.
Wenn Ihre Rowids ziemlich voll sind (dh nur wenige Löschungen), können Sie Folgendes tun (verwenden Sie
(select max(rowid) from foo)+1
anstelle von, ummax(rowid)+1
eine bessere Leistung zu erzielen, wie in den Kommentaren erläutert):Wenn Sie Löcher haben, versuchen Sie manchmal, eine nicht vorhandene Zeilen-ID auszuwählen, und die Auswahl gibt eine leere Ergebnismenge zurück. Wenn dies nicht akzeptabel ist, können Sie einen Standardwert wie den folgenden angeben:
Diese zweite Lösung ist nicht perfekt: Die Wahrscheinlichkeitsverteilung ist in der letzten Zeile (der mit der höchsten Zeilen-ID) höher. Wenn Sie jedoch häufig Daten zur Tabelle hinzufügen, wird dies zu einem sich bewegenden Ziel, und die Verteilung der Wahrscheinlichkeiten sollte höher sein viel besser.
Eine weitere Lösung: Wenn Sie häufig zufällige Elemente aus einer Tabelle mit vielen Löchern auswählen, möchten Sie möglicherweise eine Tabelle erstellen, die die Zeilen der ursprünglichen Tabelle enthält, die in zufälliger Reihenfolge sortiert sind:
Füllen Sie dann regelmäßig die Tabelle random_foo neu aus
Und um eine zufällige Zeile auszuwählen, können Sie meine erste Methode verwenden (hier gibt es keine Löcher). Natürlich hat diese letzte Methode einige Parallelitätsprobleme, aber die Neuerstellung von random_foo ist eine Wartungsoperation, die wahrscheinlich nicht sehr häufig vorkommt.
Eine weitere Möglichkeit, die ich kürzlich auf einer Mailingliste gefunden habe , besteht darin, beim Löschen einen Auslöser zu setzen, um die Zeile mit der größten Zeilen-ID in die aktuell gelöschte Zeile zu verschieben, sodass keine Löcher mehr vorhanden sind.
Beachten Sie zum Schluss, dass das Verhalten der automatischen Inkrementierung von Zeilen-ID und ganzzahligem Primärschlüssel nicht identisch ist (bei Zeilen-ID wird beim Einfügen einer neuen Zeile max (Zeilen-ID) +1 ausgewählt, wobei es der höchste Wert ist, der jemals gesehen wurde + 1 für ein Primärschlüssel), sodass die letzte Lösung nicht mit einer automatischen Inkrementierung in random_foo funktioniert, die anderen Methoden jedoch.
quelle
SELECT max(rowid) + 1
wird eine langsame Abfrage sein - es erfordert einen vollständigen Tabellenscan. SQLite optimiert nur die AbfrageSELECT max(rowid)
. Daher würde diese Antwort verbessert werden durch:select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1));
Weitere Informationen finden Sie hier: sqlite.1065341.n5.nabble.com/…Sie müssen "order by RANDOM ()" in Ihre Abfrage aufnehmen.
Beispiel:
Sehen wir uns ein vollständiges Beispiel an
Einfügen einiger Werte:
Eine Standardauswahl:
Ein ausgewählter Zufall:
* Bei jeder Auswahl ist die Reihenfolge anders.Wenn Sie nur eine Zeile zurückgeben möchten
* Bei jeder Auswahl ist die Rückgabe anders.quelle
Wie wäre es mit:
Wählen Sie dann eine Zufallszahl m in [0, n) und
Sie können die erste Nummer ( n ) sogar irgendwo speichern und nur aktualisieren, wenn sich die Datenbankanzahl ändert. Auf diese Weise müssen Sie nicht jedes Mal SELECT COUNT ausführen.
quelle
OFFSET
, abhängig von der Größe des Versatzes zu steigen scheint - Zeile 2 ist schnell, Zeile 2 Millionen dauert eine Weile, selbst wenn alle Daten in der festen Größe sind und es sollte in der Lage sein, direkt danach zu suchen. Zumindest sieht es in SQLite 3.7.13 so aus.quelle
Hier ist eine Modifikation der Lösung von @ ank:
Diese Lösung funktioniert auch für Indizes mit Lücken, da wir einen Offset in einem Bereich [0, count] randomisieren.
MAX
wird verwendet, um einen Fall mit leerer Tabelle zu behandeln.Hier sind einfache Testergebnisse für eine Tabelle mit 16.000 Zeilen:
quelle
Ich habe die folgende Lösung für die großen sqlite3-Datenbanken entwickelt :
Schließlich fügen Sie +1 hinzu, um zu verhindern, dass die Zeilen-ID gleich 0 ist.
quelle