Zufälliger Datensatz in ActiveRecord

151

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.

Jyunderwood
quelle
2
2 Punkte, die eine Antwort helfen könnten. 1. Wie gleichmäßig verteilt sind Ihre IDs, sind sie sequentiell? 2. Wie zufällig muss es sein? Gut genug zufällig oder echt zufällig?
Michael
Es handelt sich um sequentielle IDs, die automatisch von Activerecord generiert werden und nur gut genug sein müssen.
Jyunderwood
1
Dann ist Ihre vorgeschlagene Lösung nahezu ideal :) Ich würde "SELECT MAX (id) FROM table_name" anstelle von COUNT (*) verwenden, da gelöschte Zeilen etwas besser behandelt werden, andernfalls ist der Rest in Ordnung. Kurz gesagt, wenn "gut genug" in Ordnung ist, müssen Sie nur eine Methode haben, die eine Verteilung annimmt, die nahe an der liegt, die Sie tatsächlich haben. Wenn es einheitlich ist und auch wie Sie gesagt haben, funktioniert einfacher Rand großartig.
Michael
1
Dies funktioniert nicht, wenn Sie Zeilen gelöscht haben.
Venkat D.

Antworten:

136

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 .

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

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.

Toby Hede
quelle
2
Der Code Model.find(:offset => offset).firstwird einen Fehler auslösen. Ich denke, Model.first(:offset => offset)könnte besser abschneiden.
Harish Shetty
1
Ja, ich habe mit Rails 3 gearbeitet und bin immer wieder verwirrt über die Abfrageformate zwischen den Versionen.
Toby Hede
7
Beachten Sie, dass die Verwendung von Offset bei großen Datenmengen sehr langsam ist, da tatsächlich ein Index-Scan erforderlich ist (oder ein Tabellenscan, falls ein Clustered-Index wie InnoDB verwendet wird). Mit anderen Worten, es ist eine O (N) -Operation, aber "WHERE id> = # {rand_id} ORDER BY id ASC LIMIT 1" ist O (log N), was viel schneller ist.
Kenn
15
Beachten Sie, dass der Offset-Ansatz nur einen einzigen zufällig gefundenen Datenpunkt liefert (der erste, alle nachfolgenden werden immer noch nach ID sortiert). Wenn Sie mehrere zufällig ausgewählte Datensätze benötigen, müssen Sie diesen Ansatz mehrmals verwenden oder die von Ihrer Datenbank bereitgestellte Methode der zufälligen Reihenfolge verwenden, dh Thing.order("RANDOM()").limit(100)für 100 zufällig ausgewählte Einträge. (Beachten Sie, dass es RANDOM()in PostgreSQL und RAND()in MySQL ist ... nicht so portabel, wie Sie es vielleicht möchten.)
Florian Pilz
3
Funktioniert bei Rails 4 nicht für mich Model.offset(offset).first.
Mahemoff
206

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.

Model.order(Arel.sql('RANDOM()')).first

Schienen 5, 4

Verwenden Sie in Rails 4 und 5 Postgresql oder SQLite mit RANDOM():

Model.order('RANDOM()').first

Vermutlich würde das gleiche für MySQL mit funktionierenRAND()

Model.order('RAND()').first

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 limitKlausel hinzufügen .

Mohamad
quelle
4
"Random ()" funktioniert auch in SQLite. Für diejenigen von uns, die noch auf SQLite entwickeln und Postgres in der Produktion ausführen, funktioniert Ihre Lösung in beiden Umgebungen.
Wuliwong
5
Ich habe dafür einen Benchmark gegen die akzeptierte Antwort erstellt. In Postgresql 9.4 ist der Ansatz dieser Antwort etwa doppelt so schnell.
Panmari
3
Sieht so aus, als würde es auf mysql webtrenches.com/post.cfm/avoid-rand-in-mysql
Prakash Murthy
Dies ist die schnellste Lösung
Sergio Belevskij
1
"Nicht-Attribut-Argumente werden in Rails 6.0 nicht zugelassen. Diese Methode sollte nicht mit vom Benutzer bereitgestellten Werten wie Anforderungsparametern oder Modellattributen aufgerufen werden. Bekannte sichere Werte können übergeben werden, indem sie in Arel.sql () eingeschlossen werden."
Trenton Tyler
73

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

Model.first(:order => "RANDOM()") # postgres example
Semanticart
quelle
7
ORDER BY RAND () für MySQL endet mit zunehmender Datenmenge in einer schrecklichen Laufzeit. Es ist nicht wartbar (abhängig von den Zeitanforderungen) und beginnt bereits bei Tausenden von Zeilen.
Michael
Michael bringt einen großartigen Punkt vor (das gilt auch für andere DBs). Im Allgemeinen möchten Sie in einer dynamischen Aktion keine zufälligen Zeilen aus großen Tabellen auswählen. Caching ist dein Freund. Überdenken, was Sie erreichen möchten, ist möglicherweise auch keine schlechte Idee.
Semanticart
1
RAND () in MySQL auf einer Tabelle mit ungefähr einer Million Zeilen zu bestellen, ist slooooooooooooooooooooow.
Subimage
24
Funktioniert nicht mehr Verwenden Sie Model.order("RANDOM()").firststattdessen.
Phil Pirozhkov
Langsam und datenbankspezifisch. ActiveRecord soll nahtlos zwischen Datenbanken funktionieren, daher sollten Sie diese Methode nicht verwenden.
Dex
29

Benchmarking dieser beiden Methoden unter MySQL 5.1.49, Ruby 1.9.2p180 in einer Produkttabelle mit + 5 Millionen Datensätzen:

def random1
  rand_id = rand(Product.count)
  rand_record = Product.first(:conditions => [ "id >= ?", rand_id])
end

def random2
  if (c = Product.count) != 0
    Product.find(:first, :offset =>rand(c))
  end
end

n = 10
Benchmark.bm(7) do |x|
  x.report("next id:") { n.times {|i| random1 } }
  x.report("offset:")  { n.times {|i| random2 } }
end


             user     system      total        real
next id:  0.040000   0.000000   0.040000 (  0.225149)
offset :  0.020000   0.000000   0.020000 ( 35.234383)

Der Offset in MySQL scheint viel langsamer zu sein.

EDIT habe ich auch versucht

Product.first(:order => "RAND()")

Aber ich musste es nach ~ 60 Sekunden töten. MySQL war "Kopieren in eine tmp-Tabelle auf der Festplatte". Das wird nicht funktionieren.

dkam
quelle
1
Für diejenigen, die nach mehr Tests suchen, wie lange ein echter Zufallsansatz dauert: Ich habe Thing.order("RANDOM()").firsteine 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.
Florian Pilz
6
Rubys Rand-Methode gibt eine weniger als die angegebene Zahl zurück, sodass Sie möchten rand_id = rand(Product.count) + 1oder nie den letzten Datensatz erhalten.
Ritchie
4
Hinweis random1funktioniert 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.)
Nicholas
Die Verwendung random2kann durch die #orderVerwendung einer indizierten Spalte verbessert werden .
Carson Reinke
18

Es muss nicht so schwer sein.

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

pluckGibt ein Array aller IDs in der Tabelle zurück. Die sampleMethode 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.

User.where(favorite_day: "Friday").pluck(:id)

Und wählen Sie dabei einen zufälligen Benutzer aus, der freitags lieber mag als nur irgendeinen Benutzer.

Niels B.
quelle
8
Dies ist sauber und funktioniert für einen kleinen Tisch oder eine einmalige Verwendung. Beachten Sie jedoch, dass es nicht skalierbar ist. Auf einem 3M-Tisch dauert das Zupfen von IDs in MariaDB ungefähr 15 Sekunden.
Mahemoff
2
Das ist ein guter Punkt. Haben Sie eine alternative Lösung gefunden, die schneller ist und dieselben Eigenschaften beibehält?
Niels B.
Behält die akzeptierte Offset-Lösung nicht die gleichen Eigenschaften bei?
Mahemoff
Nein, es unterstützt keine Bedingungen und hat nicht die gleiche Auswahlwahrscheinlichkeit für Tabellen mit gelöschten Datensätzen.
Niels B.
1
Wenn Sie die Einschränkungen beim Zählen und Auswählen mit einem Versatz anwenden, sollte die Technik funktionieren. Ich stellte mir vor, es nur auf die Zählung anzuwenden.
Niels B.
15

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 sampleMethode aus der Ruby Array-Klasse verwenden , mit der Sie ein zufälliges Element auswählen können aus einem Array.

Model.all.sample

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.

Ryan Atallah
quelle
99
Mach das nicht. Je.
Zabba
5
Wenn Ihre Datenbank 100.000 Zeilen enthält, müssen alle diese Zeilen in den Speicher geladen werden.
Venkat D.
3
Natürlich wird es nicht für Produktions-Echtzeitcode empfohlen, aber ich mag diese Lösung. Es ist sehr klar, sie für spezielle Situationen wie das Seeding der Datenbank mit gefälschten Werten zu verwenden.
Fguillen
13
Bitte - sag niemals nie. Dies ist eine großartige Lösung für das Debuggen während der Entwicklungszeit, wenn die Tabelle klein ist. (Und wenn Sie Proben nehmen, ist das Debuggen möglicherweise der Anwendungsfall).
Mahemoff
Ich benutze zum Säen und ist gut für mich. Außerdem funktioniert Model.all.sample (n) auch :)
Arnaldo Ignacio Gaspar Véjar
13

Ich habe einen Rails 3 Edelstein gemacht, um damit umzugehen:

https://github.com/spilliton/randumb

Es ermöglicht Ihnen, solche Dinge zu tun:

Model.where(:column => "value").random(10)
Spilliton
quelle
7
In der Dokumentation dieses Edelsteins erklären sie: "Randumb greift einfach eine zusätzliche ORDER BY RANDOM()(oder RAND()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.
Nicolas
8

Ich benutze dies so oft von der Konsole aus, dass ich ActiveRecord in einem Initialisierer erweitere - Rails 4 Beispiel:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

Ich kann dann anrufen Foo.random, um eine zufällige Aufzeichnung zurückzubringen.

Knotty66
quelle
1
brauchen Sie limit(1)? ActiveRecord#firstsollte klug genug sein, das zu tun.
Tokand
6

Eine Abfrage in Postgres:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

Mit einem Offset zwei Abfragen:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)
Thomas Klemm
quelle
1
Keine Notwendigkeit für -1, Rand zählt bis zu num - 1
anemaria20
Danke, geändert: +1:
Thomas Klemm
5

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:

  1. RAND () mit a limitist ein klarer Gewinner.
  2. Verwenden Sie nicht pluck+ sample.
def random1
  Model.find(rand((Model.last.id + 1)))
end

def random2
  Model.order("RAND()").limit(1)
end

def random3
  Model.pluck(:id).sample
end

n = 100
Benchmark.bm(7) do |x|
  x.report("find:")    { n.times {|i| random1 } }
  x.report("order:")   { n.times {|i| random2 } }
  x.report("pluck:")   { n.times {|i| random3 } }
end

              user     system      total        real
find:     0.090000   0.000000   0.090000 (  0.127585)
order:    0.000000   0.000000   0.000000 (  0.002095)
pluck:    6.150000   0.000000   6.150000 (  8.292074)

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!

Sam
quelle
3

Sie können die ArrayMethode verwenden sample. Die Methode samplegibt ein zufälliges Objekt aus einem Array zurück. Um es zu verwenden, müssen Sie nur eine einfache ActiveRecordAbfrage ausführen, die eine Sammlung zurückgibt , zum Beispiel:

User.all.sample

wird so etwas zurückgeben:

#<User id: 25, name: "John Doe", email: "[email protected]", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">
trejo08
quelle
Ich würde nicht empfehlen, mit Array-Methoden zu arbeiten, während Sie AR verwenden. Dieser Weg dauert fast achtmal so lange order('rand()').limit(1)wie "der gleiche" Job (mit ~ 10.000 Datensätzen).
Sebastian Palma
3

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:

  1. quick_random_records kosten nur 4.6mstotal.

Geben Sie hier die Bildbeschreibung ein

  1. die User.order('RAND()').limit(10)Kosten 733.0ms.

Geben Sie hier die Bildbeschreibung ein

  1. Der akzeptierte Antwortansatz offsetkostet 245.4mstotal.

Geben Sie hier die Bildbeschreibung ein

  1. die User.all.sample(10)Anflugkosten 573.4ms.

Geben Sie hier die Bildbeschreibung ein


Hinweis: Meine Tabelle hat nur 120.000 Benutzer. Je mehr Datensätze Sie haben, desto größer wird der Leistungsunterschied sein.

Derek Fan
quelle
2

Wenn Sie zufällige Ergebnisse innerhalb des angegebenen Bereichs auswählen müssen :

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)
Juri Karpowitsch
quelle
1

Die Ruby-Methode zum zufälligen Auswählen eines Elements aus einer Liste lautet sample. Um ein effizientes samplefür ActiveRecord zu erstellen und basierend auf den vorherigen Antworten, habe ich verwendet:

module ActiveRecord
  class Base
    def self.sample
      offset(rand(size)).first
    end
  end
end

Ich lege das ein lib/ext/sample.rbund lade es dann mit config/initializers/monkey_patches.rb:

Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }

Dies ist eine Abfrage, wenn die Größe des Modells bereits zwischengespeichert ist, und ansonsten zwei.

Dan Kohn
quelle
1

Rails 4.2 und Oracle :

Für Orakel können Sie einen Bereich für Ihr Modell wie folgt festlegen:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

oder

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

Und dann für ein Beispiel nennen Sie es so:

Model.random_order.take(10)

oder

Model.random_order.limit(5)

Natürlich können Sie auch eine Bestellung ohne Umfang wie folgt aufgeben:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively
Mahatmanich
quelle
Sie können dies auch mit Postgres mit order('random()'und MySQL mit tun order('rand()'). Dies ist definitiv die beste Antwort.
Jrochkind
1

Versuchen Sie für die MySQL-Datenbank zuerst: Model.order ("RAND ()")

Vadim Eremeev
quelle
Dies funktioniert nicht auf MySQL. Sie sollten zumindest angeben, mit welcher DB-Engine dies funktionieren soll
Arnold Roa
Entschuldigung, es gab Tippfehler. Jetzt behoben. Sollte für
MySQL
1

Wenn Sie PostgreSQL 9.5+ verwenden, können Sie dies nutzen TABLESAMPLE Sie einen zufälligen Datensatz auswählen.

Die beiden Standard-Stichprobenmethoden ( SYSTEMund BERNOULLI) erfordern, dass Sie die Anzahl der zurückzugebenden Zeilen als Prozentsatz der Gesamtzahl der Zeilen in der Tabelle angeben.

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

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_rowsModul , mit dem Sie die Anzahl der Zeilen angeben können, die direkt zurückgegeben werden sollen.

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

Um dies in ActiveRecord zu verwenden, aktivieren Sie zuerst die Erweiterung innerhalb einer Migration:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

Ändern Sie dann die fromKlausel der Abfrage:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

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 .

Adam Sheehan
quelle
1

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.

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

Hier sind die Daten, die 2000 Mal in meiner 100.000-Zeilen-Tabelle ausgeführt werden, um zufällige Daten auszuschließen

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)
Mendoza
quelle
1

Sehr alte Frage aber mit:

rand_record = Model.all.shuffle

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:

rand_record = Model.all.shuffle.first
Gregdebrick
quelle
1
Nicht die beste Option, da hierdurch alle Datensätze in den Speicher geladen werden. Auch shuffle.first==.sample
Andrew Rozhenko
0

Ich bin brandneu bei RoR, aber ich habe Folgendes für mich erledigt:

 def random
    @cards = Card.all.sort_by { rand }
 end

Es kam von:

Wie kann man ein Array in Ruby zufällig sortieren (verschlüsseln)?

Aaron Pennington
quelle
4
Das Schlimme daran ist, dass alle Karten aus der Datenbank geladen werden. Es ist effizienter, dies innerhalb der Datenbank zu tun.
Anton Kuzmin
Sie können Arrays auch mit mischen array.shuffle. Beachten Sie jedoch, dass Card.allalle Kartendatensätze in den Speicher geladen werden, was umso ineffizienter wird, je mehr Objekte wir verwenden.
Thomas Klemm
0

Was ist zu tun:

rand_record = Model.find(Model.pluck(:id).sample)

Für mich ist viel klar

poramo
quelle
0

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:

 def random1
2.4.1 :071?>   Category.find(rand(1..Category.count))
2.4.1 :072?>   end
 => :random1
2.4.1 :073 > def random2
2.4.1 :074?>    Category.offset(rand(1..Category.count))
2.4.1 :075?>   end
 => :random2
2.4.1 :076 > def random3
2.4.1 :077?>   Category.offset(rand(1..Category.count)).limit(rand(1..3))
2.4.1 :078?>   end
 => :random3
2.4.1 :079 > def random4
2.4.1 :080?>    Category.pluck(rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 >     end
 => :random4
2.4.1 :083 > n = 100
 => 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 >     x.report("find") { n.times {|i| random1 } }
2.4.1 :086?>   x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?>   x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?>   x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?>   end

                  user      system      total     real
find            0.070000   0.010000   0.080000 (0.118553)
offset          0.040000   0.010000   0.050000 (0.059276)
offset_limit    0.050000   0.000000   0.050000 (0.060849)
pluck           0.070000   0.020000   0.090000 (0.099065)
rld
quelle
0

.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 sie limit1 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)")wo 0.5Mittel 0.5%. Ich finde jedoch, dass diese Lösung immer noch langsam ist, wenn Sie WHEREBedingungen haben, die viele Zeilen herausfiltern. Ich denke, das liegt daran, dass TABLESAMPLE 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:

products_scope.limit(sample_size).sample(limit)

wo sample_sizekann sein 100(aber nicht zu groß, sonst ist es langsam und verbraucht viel Speicher) und limitkann sein 1. Beachten Sie, dass dies zwar schnell ist, aber nicht wirklich zufällig, sondern sample_sizenur 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.

Linh Dam
quelle
0

Neben der Verwendung RANDOM()können Sie dies auch in einen Bereich werfen:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

Wenn Sie sich das nicht als Bereich vorstellen, werfen Sie es einfach in eine Klassenmethode. Funktioniert jetzt Thing.randomzusammen mit Thing.random(n).

Damien Roche
quelle
0

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:

  • Meinst du, gib mir ein Element, das mir egal ist, seine Position? dann ist es genug.
  • Wenn Sie nun "Geben Sie mir ein Element mit einer angemessenen Wahrscheinlichkeit, dass wiederholte Experimente mir andere Elemente aus dem Satz ergeben" meinen, dann erzwingen Sie das "Glück" mit einer der in den anderen Antworten genannten Methoden.

Zum Beispiel könnten zum Testen Beispieldaten sowieso zufällig erstellt worden sein, takewas mehr als genug ist, und um ehrlich zu sein, sogar first.

https://guides.rubyonrails.org/active_record_querying.html#take

jgomo3
quelle