Ich brauche einen zufälligen Datensatz aus einer Tabelle über ActiveRecord. Ich bin dem Beispiel von gefolgt Jamis Buck aus dem Jahr 2006 .
Ich bin jedoch auch über eine Google-Suche auf einen anderen Weg gestoßen (kann aufgrund neuer Benutzereinschränkungen keinen Link zuordnen):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
Ich bin gespannt, wie andere es hier gemacht haben oder ob jemand weiß, wie es effizienter wäre.
ruby-on-rails
random
rails-activerecord
Jyunderwood
quelle
quelle
Antworten:
Ich habe keinen idealen Weg gefunden, dies ohne mindestens zwei Abfragen zu tun.
Im Folgenden wird eine zufällig generierte Zahl (bis zur aktuellen Datensatzanzahl) als Offset verwendet .
Um ehrlich zu sein, habe ich gerade ORDER BY RAND () oder RANDOM () verwendet (abhängig von der Datenbank). Es ist kein Leistungsproblem, wenn Sie kein Leistungsproblem haben.
quelle
Model.find(:offset => offset).first
wird einen Fehler auslösen. Ich denke,Model.first(:offset => offset)
könnte besser abschneiden.Thing.order("RANDOM()").limit(100)
für 100 zufällig ausgewählte Einträge. (Beachten Sie, dass esRANDOM()
in PostgreSQL undRAND()
in MySQL ist ... nicht so portabel, wie Sie es vielleicht möchten.)Model.offset(offset).first
.Schienen 6
Wie von Jason in den Kommentaren angegeben, sind in Rails 6 Argumente ohne Attribute nicht zulässig. Sie müssen den Wert in eine
Arel.sql()
Anweisung einschließen.Schienen 5, 4
Verwenden Sie in Rails 4 und 5 Postgresql oder SQLite mit
RANDOM()
:Vermutlich würde das gleiche für MySQL mit funktionieren
RAND()
Dies ist ungefähr 2,5-mal schneller als der Ansatz in der akzeptierten Antwort .
Vorsichtsmaßnahme : Dies ist bei großen Datenmengen mit Millionen von Datensätzen langsam. Daher möchten Sie möglicherweise eine
limit
Klausel hinzufügen .quelle
Ihr Beispielcode verhält sich nach dem Löschen von Datensätzen ungenau (Artikel mit niedrigeren IDs werden zu Unrecht bevorzugt).
Sie sind wahrscheinlich besser dran, wenn Sie die zufälligen Methoden in Ihrer Datenbank verwenden. Diese variieren je nachdem, welche Datenbank Sie verwenden, aber: order => "RAND ()" funktioniert für MySQL und: order => "RANDOM ()" funktioniert für Postgres
quelle
Model.order("RANDOM()").first
stattdessen.Benchmarking dieser beiden Methoden unter MySQL 5.1.49, Ruby 1.9.2p180 in einer Produkttabelle mit + 5 Millionen Datensätzen:
Der Offset in MySQL scheint viel langsamer zu sein.
EDIT habe ich auch versucht
Aber ich musste es nach ~ 60 Sekunden töten. MySQL war "Kopieren in eine tmp-Tabelle auf der Festplatte". Das wird nicht funktionieren.
quelle
Thing.order("RANDOM()").first
eine Tabelle mit 250.000 Einträgen ausprobiert - die Abfrage wurde in weniger als einer halben Sekunde abgeschlossen. (PostgreSQL 9.0, REE 1.8.7, 2 x 2,66 GHz-Kerne) Das ist schnell genug für mich, da ich eine einmalige "Bereinigung" durchführe.rand_id = rand(Product.count) + 1
oder nie den letzten Datensatz erhalten.random1
funktioniert nicht, wenn Sie jemals eine Zeile in der Tabelle löschen. (Die Anzahl ist geringer als die maximale ID und Sie können niemals Zeilen mit hohen IDs auswählen.)random2
kann durch die#order
Verwendung einer indizierten Spalte verbessert werden .Es muss nicht so schwer sein.
pluck
Gibt ein Array aller IDs in der Tabelle zurück. Diesample
Methode im Array gibt eine zufällige ID aus dem Array zurück.Dies sollte bei gleicher Auswahlwahrscheinlichkeit und Unterstützung für Tabellen mit gelöschten Zeilen gut funktionieren. Sie können es sogar mit Einschränkungen mischen.
Und wählen Sie dabei einen zufälligen Benutzer aus, der freitags lieber mag als nur irgendeinen Benutzer.
quelle
Es wird nicht empfohlen, diese Lösung zu verwenden. Wenn Sie jedoch aus irgendeinem Grund wirklich zufällig einen Datensatz auswählen möchten, während Sie nur eine Datenbankabfrage durchführen, können Sie die
sample
Methode aus der Ruby Array-Klasse verwenden , mit der Sie ein zufälliges Element auswählen können aus einem Array.Diese Methode erfordert nur Datenbankabfragen, ist jedoch erheblich langsamer als Alternativen
Model.offset(rand(Model.count)).first
, für die zwei Datenbankabfragen erforderlich sind, obwohl letztere weiterhin bevorzugt werden.quelle
Ich habe einen Rails 3 Edelstein gemacht, um damit umzugehen:
https://github.com/spilliton/randumb
Es ermöglicht Ihnen, solche Dinge zu tun:
quelle
ORDER BY RANDOM()
(oderRAND()
für MySQL) zu Ihrer Anfrage an." - Daher gelten die Kommentare zu schlechter Leistung, die in den Kommentaren zur Antwort von @semanticart erwähnt werden, auch bei Verwendung dieses Edelsteins. Aber zumindest ist es DB-unabhängig.Ich benutze dies so oft von der Konsole aus, dass ich ActiveRecord in einem Initialisierer erweitere - Rails 4 Beispiel:
Ich kann dann anrufen
Foo.random
, um eine zufällige Aufzeichnung zurückzubringen.quelle
limit(1)
?ActiveRecord#first
sollte klug genug sein, das zu tun.Eine Abfrage in Postgres:
Mit einem Offset zwei Abfragen:
quelle
Das Lesen all dieser Informationen gab mir nicht viel Vertrauen darüber, welche davon in meiner speziellen Situation mit Rails 5 und MySQL / Maria 5.5 am besten funktionieren würden. Also habe ich einige der Antworten auf ~ 65000 Datensätzen getestet und habe zwei Take Aways:
limit
ist ein klarer Gewinner.pluck
+sample
.Diese Antwort synthetisiert, validiert und aktualisiert Mohammeds Antwort sowie Nami WANGs Kommentar dazu und Florian Pilz 'Kommentar zur akzeptierten Antwort - bitte senden Sie ihnen Stimmen!
quelle
Sie können die
Array
Methode verwendensample
. Die Methodesample
gibt ein zufälliges Objekt aus einem Array zurück. Um es zu verwenden, müssen Sie nur eine einfacheActiveRecord
Abfrage ausführen, die eine Sammlung zurückgibt , zum Beispiel:wird so etwas zurückgeben:
quelle
order('rand()').limit(1)
wie "der gleiche" Job (mit ~ 10.000 Datensätzen).Empfehlen Sie dieses Juwel dringend für zufällige Datensätze, das speziell für Tabellen mit vielen Datenzeilen entwickelt wurde:
https://github.com/haopingfan/quick_random_records
Alle anderen Antworten funktionieren mit einer großen Datenbank schlecht, mit Ausnahme dieses Edelsteins:
4.6ms
total.User.order('RAND()').limit(10)
Kosten733.0ms
.offset
kostet245.4ms
total.User.all.sample(10)
Anflugkosten573.4ms
.Hinweis: Meine Tabelle hat nur 120.000 Benutzer. Je mehr Datensätze Sie haben, desto größer wird der Leistungsunterschied sein.
quelle
Wenn Sie zufällige Ergebnisse innerhalb des angegebenen Bereichs auswählen müssen :
quelle
Die Ruby-Methode zum zufälligen Auswählen eines Elements aus einer Liste lautet
sample
. Um ein effizientessample
für ActiveRecord zu erstellen und basierend auf den vorherigen Antworten, habe ich verwendet:Ich lege das ein
lib/ext/sample.rb
und lade es dann mitconfig/initializers/monkey_patches.rb
:Dies ist eine Abfrage, wenn die Größe des Modells bereits zwischengespeichert ist, und ansonsten zwei.
quelle
Rails 4.2 und Oracle :
Für Orakel können Sie einen Bereich für Ihr Modell wie folgt festlegen:
oder
Und dann für ein Beispiel nennen Sie es so:
oder
Natürlich können Sie auch eine Bestellung ohne Umfang wie folgt aufgeben:
quelle
order('random()'
und MySQL mit tunorder('rand()')
. Dies ist definitiv die beste Antwort.Versuchen Sie für die MySQL-Datenbank zuerst: Model.order ("RAND ()")
quelle
Wenn Sie PostgreSQL 9.5+ verwenden, können Sie dies nutzen
TABLESAMPLE
Sie einen zufälligen Datensatz auswählen.Die beiden Standard-Stichprobenmethoden (
SYSTEM
undBERNOULLI
) erfordern, dass Sie die Anzahl der zurückzugebenden Zeilen als Prozentsatz der Gesamtzahl der Zeilen in der Tabelle angeben.Dies erfordert die Kenntnis der Anzahl der Datensätze in der Tabelle, um den geeigneten Prozentsatz auszuwählen, der möglicherweise nicht schnell zu finden ist. Glücklicherweise gibt es das
tsm_system_rows
Modul , mit dem Sie die Anzahl der Zeilen angeben können, die direkt zurückgegeben werden sollen.Um dies in ActiveRecord zu verwenden, aktivieren Sie zuerst die Erweiterung innerhalb einer Migration:
Ändern Sie dann die
from
Klausel der Abfrage:Ich weiß nicht, ob die
SYSTEM_ROWS
Stichprobenmethode völlig zufällig ist oder nur die erste Zeile einer zufälligen Seite zurückgibt.Die meisten dieser Informationen stammen aus einem 2ndQuadrant-Blogbeitrag von Gulcin Yildirim .
quelle
Nachdem ich so viele Antworten gesehen hatte, beschloss ich, sie alle in meiner PostgreSQL (9.6.3) -Datenbank zu vergleichen. Ich benutze eine kleinere 100.000-Tabelle und habe die Model.order ("RANDOM ()") entfernt, zuerst, da sie bereits zwei Größenordnungen langsamer war.
Bei Verwendung einer Tabelle mit 2.500.000 Einträgen mit 10 Spalten war der zweifelsohne Gewinner die Zupfmethode, die fast achtmal schneller war als der Zweitplatzierte (Offset. Ich habe dies nur auf einem lokalen Server ausgeführt, damit die Anzahl möglicherweise aufgeblasen wird, aber sie ist größer genug als das Zupfen Die Methode wird am Ende verwendet. Es ist auch erwähnenswert, dass dies zu Problemen führen kann, wenn Sie mehr als ein Ergebnis gleichzeitig zupfen, da jedes einzelne davon einzigartig oder auch weniger zufällig ist.
Zupfen gewinnt 100 Mal in meiner 25.000.000 Zeilentabelle. Bearbeiten: Dieses Mal wird das Zupfen in die Schleife aufgenommen. Wenn ich es herausnehme, läuft es ungefähr so schnell wie eine einfache Iteration der ID. Jedoch; Es nimmt eine ganze Menge RAM in Anspruch.
Hier sind die Daten, die 2000 Mal in meiner 100.000-Zeilen-Tabelle ausgeführt werden, um zufällige Daten auszuschließen
quelle
Sehr alte Frage aber mit:
Sie haben ein Array von Datensätzen, die nach zufälliger Reihenfolge sortiert sind. Keine Notwendigkeit Edelsteine oder Skripte.
Wenn Sie einen Datensatz möchten:
quelle
shuffle.first
==.sample
Ich bin brandneu bei RoR, aber ich habe Folgendes für mich erledigt:
Es kam von:
Wie kann man ein Array in Ruby zufällig sortieren (verschlüsseln)?
quelle
array.shuffle
. Beachten Sie jedoch, dassCard.all
alle Kartendatensätze in den Speicher geladen werden, was umso ineffizienter wird, je mehr Objekte wir verwenden.Was ist zu tun:
Für mich ist viel klar
quelle
Ich versuche dieses Beispiel von Sam in meiner App mit den Schienen 4.2.8 von Benchmark (ich habe 1..Category.count für random gesetzt, da es einen Fehler erzeugt, wenn der Zufall eine 0 annimmt (ActiveRecord :: RecordNotFound: Konnte nicht gefunden werden) Kategorie mit 'id' = 0)) und die Mine war:
quelle
.order('RANDOM()').limit(limit)
sieht ordentlich aus, ist aber für große Tabellen langsam, da alle Zeilen abgerufen und sortiert werden müssen, auch wenn sielimit
1 sind (intern in der Datenbank, aber nicht in Rails). Ich bin mir bei MySQL nicht sicher, aber das passiert in Postgres. Weitere Erklärungen hier und hier .Eine Lösung für große Tische ist
.from("products TABLESAMPLE SYSTEM(0.5)")
wo0.5
Mittel0.5%
. Ich finde jedoch, dass diese Lösung immer noch langsam ist, wenn SieWHERE
Bedingungen haben, die viele Zeilen herausfiltern. Ich denke, das liegt daran, dassTABLESAMPLE SYSTEM(0.5)
alle Zeilen zuvor abgerufen wurdenWHERE
Bedingungen zutreffen.Eine andere Lösung für große Tabellen (aber nicht sehr zufällig) ist:
wo
sample_size
kann sein100
(aber nicht zu groß, sonst ist es langsam und verbraucht viel Speicher) undlimit
kann sein1
. Beachten Sie, dass dies zwar schnell ist, aber nicht wirklich zufällig, sondernsample_size
nur innerhalb von Datensätzen zufällig .PS: Benchmark-Ergebnisse in den obigen Antworten sind nicht zuverlässig (zumindest in Postgres), da einige DB-Abfragen, die zum zweiten Mal ausgeführt werden, dank des DB-Caches erheblich schneller sein können als zum ersten Mal. Und leider gibt es keine einfache Möglichkeit, den Cache in Postgres zu deaktivieren, um diese Benchmarks zuverlässig zu machen.
quelle
Neben der Verwendung
RANDOM()
können Sie dies auch in einen Bereich werfen:Wenn Sie sich das nicht als Bereich vorstellen, werfen Sie es einfach in eine Klassenmethode. Funktioniert jetzt
Thing.random
zusammen mitThing.random(n)
.quelle
Abhängig von der Bedeutung von "zufällig" und dem, was Sie tatsächlich tun möchten,
take
könnte dies ausreichen.Mit der "Bedeutung" des Zufalls meine ich:
Zum Beispiel könnten zum Testen Beispieldaten sowieso zufällig erstellt worden sein,
take
was mehr als genug ist, und um ehrlich zu sein, sogarfirst
.https://guides.rubyonrails.org/active_record_querying.html#take
quelle