Ich habe also mehrere Beispiele für das Auffinden eines zufälligen Datensatzes in Rails 2 gefunden - die bevorzugte Methode scheint zu sein:
Thing.find :first, :offset => Rand(Thing.count)
Als Neuling bin ich mir nicht sicher, wie dies mit der neuen Suchsyntax in Rails 3 erstellt werden kann.
Also, was ist der "Rails 3 Way", um eine zufällige Aufnahme zu finden?
Thing.first(:order => "RANDOM()") # For MySQL :order => "Rand()", - thanx, @DanSingerman
# Rails 3
Thing.order("RANDOM()").first
oder
Thing.first(:offset => Rand(Thing.count))
# Rails 3
Thing.offset(Rand(Thing.count)).first
Tatsächlich funktionieren in Rails 3 alle Beispiele. Die Verwendung der Reihenfolge RANDOM
ist jedoch für große Tabellen ziemlich langsam, jedoch eher im SQL-Stil
UPD. Sie können den folgenden Trick für eine indizierte Spalte verwenden (PostgreSQL-Syntax):
select *
from my_table
where id >= trunc(
random() * (select max(id) from my_table) + 1
)
order by id
limit 1;
Ich arbeite an einem Projekt ( Rails 3.0.15, Ruby 1.9.3-p125-perf ), in dem sich die Datenbank in localhost befindet und die Benutzertabelle etwas mehr als 100K Datensätze hat.
Verwenden
bestellen durch Rand ()
ist ziemlich langsam
User.order ("Rand (id)") zuerst
wird
SELECT
users
. * FROMusers
ORDER BY Rand (id) LIMIT 1
und dauert von 8 bis 12 Sekunden um zu antworten !!
Schienenprotokoll:
User Load (11030.8ms) SELECT
users
. * FROMusers
ORDER BY Rand () LIMIT 1
von MySQL erklären
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 110165 | Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
Sie sehen, dass kein Index verwendet wird ( possible_keys = NULL ), eine temporäre Tabelle erstellt wird und ein zusätzlicher Durchlauf erforderlich ist, um den gewünschten Wert abzurufen ( extra = temporär verwenden; filesort verwenden).
Andererseits haben wir durch die Aufteilung der Abfrage in zwei Teile und die Verwendung von Ruby eine angemessene Verbesserung der Antwortzeit.
users = User.scoped.select(:id);nil
User.find( users.first( Random.Rand( users.length )).last )
(; null für Konsolennutzung)
Schienenprotokoll:
Benutzer laden (25.2ms) SELECT ID FROM
users
Benutzer laden (0.2ms) SELECTusers
. * FROMusers
WOusers
.id
= 106854 LIMIT 1
und mysqls erklären, warum:
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
| 1 | SIMPLE | users | index | NULL | index_users_on_user_type | 2 | NULL | 110165 | Using index |
+----+-------------+-------+-------+---------------+--------------------------+---------+------+--------+-------------+
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
wir können jetzt nur Indizes und den Primärschlüssel verwenden und die Arbeit etwa 500-mal schneller erledigen!
AKTUALISIEREN:
wie von icantbecool in Kommentaren ausgeführt, weist die obige Lösung einen Fehler auf, wenn Datensätze in der Tabelle gelöscht werden.
Eine Problemumgehung kann darin bestehen
users_count = User.count
User.scoped.limit(1).offset(Rand(users_count)).first
was übersetzt in zwei Abfragen
SELECT COUNT(*) FROM `users`
SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794
und läuft in etwa 500ms.
Wenn Sie Postgres verwenden
User.limit(5).order("RANDOM()")
Wenn Sie MySQL verwenden
User.limit(5).order("Rand()")
In beiden Fällen wählen Sie 5 Datensätze nach dem Zufallsprinzip aus der Tabelle Benutzer aus. Hier wird die eigentliche SQL-Abfrage in der Konsole angezeigt.
SELECT * FROM users ORDER BY RANDOM() LIMIT 5
Ich habe dafür ein Rails 3-Juwel geschaffen, das auf großen Tischen bessere Ergebnisse liefert und es Ihnen ermöglicht, Beziehungen und Bereiche zu verketten:
https://github.com/spilliton/randumb
(edit): Das Standardverhalten meines Edelsteins verwendet grundsätzlich dieselbe Vorgehensweise wie oben, aber Sie haben die Möglichkeit, die alte Methode zu verwenden, wenn Sie möchten :)
Viele der geposteten Antworten werden an ziemlich großen Tabellen (1+ Millionen Zeilen) nicht gut funktionieren. Die zufällige Bestellung dauert einige Sekunden, und das Zählen auf dem Tisch dauert ebenfalls recht lange.
Eine Lösung, die in dieser Situation gut funktioniert, ist die Verwendung von RANDOM()
mit einer Where-Bedingung:
Thing.where('RANDOM() >= 0.9').take
Bei einer Tabelle mit über einer Million Zeilen dauert diese Abfrage im Allgemeinen weniger als 2 ms.
auf geht's
#in your initializer
module ActiveRecord
class Base
def self.random
if (c = count) != 0
find(:first, :offset =>Rand(c))
end
end
end
end
Model.random #returns single random object
oder der zweite Gedanke ist
module ActiveRecord
class Base
def self.random
order("Rand()")
end
end
end
verwendungszweck:
Model.random #returns shuffled collection
Das war sehr nützlich für mich, aber ich brauchte ein bisschen mehr Flexibilität, also habe ich Folgendes getan:
Fall1: Einen zufälligen Datensatz finden quelle: trevor turk site
Fügen Sie dies dem Thing.rb-Modell hinzu
def self.random
ids = connection.select_all("SELECT id FROM things")
find(ids[Rand(ids.length)]["id"].to_i) unless ids.blank?
end
dann kannst du in deinem Controller so etwas anrufen
@thing = Thing.random
Fall2: Mehrere zufällige Datensätze finden (keine Wiederholungen) Quelle: Kann mich nicht erinnern
Ich musste 10 zufällige Datensätze ohne Wiederholungen finden
In Ihrem Controller:
thing_ids = Thing.find( :all, :select => 'id' ).map( &:id )
@things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * Rand ) } )
Es werden 10 zufällige Datensätze gefunden. Es sollte jedoch erwähnt werden, dass dies bei einer besonders großen Datenbank (Millionen Datensätze) nicht ideal ist und die Leistung beeinträchtigt wird. Is wird bis zu einigen tausend Schallplatten gut funktionieren, was für mich ausreichend war.
Die Ruby-Methode für das zufällige Auswählen eines Elements aus einer Liste lautet sample
. Ich wollte eine effiziente sample
für ActiveRecord erstellen und habe auf der Grundlage der vorherigen Antworten Folgendes verwendet:
module ActiveRecord
class Base
def self.sample
offset(Rand(size)).first
end
end
end
Ich lege dies in lib/ext/sample.rb
und lade es dann mit diesem in config/initializers/monkey_patches.rb
:
Dir[Rails.root.join('lib/ext/*.rb')].each { |file| require file }
Funktioniert in Rails 5 und ist DB-Agnostiker:
Dies in Ihrem Controller:
@quotes = Quote.offset(Rand(Quote.count - 3)).limit(3)
Sie können dies natürlich in einem Anliegen wie hier angeben.
module Randomable
extend ActiveSupport::Concern
class_methods do
def random(the_count = 1)
records = offset(Rand(count - the_count)).limit(the_count)
the_count == 1 ? records.first : records
end
end
end
dann...
class Book < ActiveRecord::Base
include Randomable
end
Dann können Sie einfach folgendes tun:
Books.random
oder
Books.random(3)
Bei Verwendung von Oracle
User.limit(10).order("DBMS_RANDOM.VALUE")
Ausgabe
SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10
Sie können sample () in ActiveRecord verwenden
Z.B.
def get_random_things_for_home_page
find(:all).sample(5)
end
Quelle: http://thinkingeek.com/2011/07/04/easily-select-random-records-Rails/
Empfehlen Sie diesen Edelstein dringend für zufällige Datensätze, der speziell für Tabellen mit vielen Datenzeilen entwickelt wurde:
https://github.com/haopingfan/quick_random_records
Alle anderen Antworten funktionieren mit großer Datenbank schlecht, mit Ausnahme dieses Edelsteins:
4.6ms
total.User.order('Rand()').limit(10)
kostet 733.0ms
.offset
-Ansatz kostet 245.4ms
insgesamt.User.all.sample(10)
Annäherungskosten 573.4ms
.Hinweis: Mein Tisch hat nur 120.000 Benutzer. Je mehr Datensätze Sie haben, desto größer ist der Leistungsunterschied.
AKTUALISIEREN:
Führen Sie eine Tabelle mit 550.000 Zeilen aus
Model.where(id: Model.pluck(:id).sample(10))
kosten 1384.0ms
gem: quick_random_records
kostet nur 6.4ms
vollständig