Wählen Sie eine zufällige Zeile aus einer SQLite-Tabelle aus

118

Ich habe eine sqliteTabelle mit folgendem Schema:

CREATE TABLE foo (bar VARCHAR)

Ich verwende diese Tabelle als Speicher für eine Liste von Zeichenfolgen.

Wie wähle ich eine zufällige Zeile aus dieser Tabelle aus?

Alex_coder
quelle
multiple stackoverflow.com/questions/4114940/…
Ciro Santilli 6 冠状 病 六四. 法轮功

Antworten:

212

Schauen Sie sich das Auswählen einer zufälligen Zeile aus einer SQLite-Tabelle an

SELECT * FROM table ORDER BY RANDOM() LIMIT 1;
Adriaan Stander
quelle
1
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:

select * from foo where rowid = (abs(random()) % (select (select max(rowid) from foo)+1)) or rowid = (select max(rowid) from node) order by rowid limit 1;

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:

create table random_foo(foo_id);

Füllen Sie dann regelmäßig die Tabelle random_foo neu aus

delete from random_foo;
insert into 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.

Suzanne Dupéron
quelle
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 order by RANDOM();

Sehen wir uns ein vollständiges Beispiel an

  1. Erstellen Sie eine Tabelle:
CREATE TABLE  quest  (
    id  INTEGER PRIMARY KEY AUTOINCREMENT,
    quest TEXT NOT NULL,
    resp_id INTEGER NOT NULL
);

Einfügen einiger Werte:

insert into quest(quest, resp_id) values ('1024/4',6), ('256/2',12), ('128/1',24);

Eine Standardauswahl:

select * from quest;

| id |   quest  | resp_id |
   1     1024/4       6
   2     256/2       12
   3     128/1       24
--

Ein ausgewählter Zufall:

select * from quest order by RANDOM();
| id |   quest  | resp_id |
   3     128/1       24
   1     1024/4       6
   2     256/2       12
--
* Bei jeder Auswahl ist die Reihenfolge anders.

Wenn Sie nur eine Zeile zurückgeben möchten

select * from quest order by RANDOM() LIMIT 1;
| id |   quest  | resp_id |
   2     256/2       12
--
* Bei jeder Auswahl ist die Rückgabe anders.

Roberto Góes
quelle
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.

Andres Kievsky
quelle
1
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.)
Yakov Galka
13
SELECT   bar
FROM     foo
ORDER BY Random()
LIMIT    1
Svetlozar Angelov
quelle
11
Wäre dies für große Tabellen nicht sehr zeitaufwändig, da zuerst der gesamte Tabelleninhalt ausgewählt wird?
Alex_coder
1
Können Sie den Umfang nicht einfach mit den Bedingungen "WHERE" einschränken?
Jldupont
10

Hier ist eine Modifikation der Lösung von @ ank:

SELECT * 
FROM table
LIMIT 1 
OFFSET ABS(RANDOM()) % MAX((SELECT COUNT(*) FROM table), 1)

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.000 user 0.000140 sys 0.000117

sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
14746
Run Time: real 0.002 user 0.000899 sys 0.000132
sqlite> select payment_id from payment limit 1 offset abs(random()) % (select count(*) from payment);
12486
Run Time: real 0.001 user 0.000952 sys 0.000103

sqlite> select payment_id from payment order by random() limit 1;
3134
Run Time: real 0.015 user 0.014022 sys 0.000309
sqlite> select payment_id from payment order by random() limit 1;
9407
Run Time: real 0.018 user 0.013757 sys 0.000208
Vokilam
quelle
4

Ich habe die folgende Lösung für die großen sqlite3-Datenbanken entwickelt :

SELECT * FROM foo WHERE rowid = abs(random()) % (SELECT max(rowid) FROM foo) + 1; 

Die Funktion abs (X) gibt den Absolutwert des numerischen Arguments X zurück.

Die Funktion random () gibt eine pseudozufällige Ganzzahl zwischen -9223372036854775808 und +9223372036854775807 zurück.

Der Operator% gibt den ganzzahligen Wert seines linken Operanden modulo seines rechten Operanden aus.

Schließlich fügen Sie +1 hinzu, um zu verhindern, dass die Zeilen-ID gleich 0 ist.

Max
quelle
1
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.
Calicoder