it-swarm.com.de

Rails 3: Holen Sie sich Zufallsdaten

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?

131
Andrew
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;
214
fl00r

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. * FROM users ORDER BY Rand (id) LIMIT 1

und dauert von 8 bis 12 Sekunden um zu antworten !!

Schienenprotokoll:

User Load (11030.8ms) SELECT users. * FROM users 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) SELECT users. * FROM users WO users.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.

29
xlembouras

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
12
icantbecool

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 :)

11
spilliton

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.

6
fivedigit

auf geht's

Rails Weg

#in your initializer
module ActiveRecord
  class Base
    def self.random
      if (c = count) != 0
        find(:first, :offset =>Rand(c))
      end
    end
  end
end

verwendungszweck

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
5
huan son

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.

4
Hishalv

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 }
4
dankohn

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.

app/Modelle/Anliegen/randomable.rb

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...

app/models/book.rb

class Book < ActiveRecord::Base
  include Randomable
end

Dann können Sie einfach folgendes tun:

Books.random

oder 

Books.random(3)
3
richardun

Bei Verwendung von Oracle

User.limit(10).order("DBMS_RANDOM.VALUE")

Ausgabe

SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10
1
Marcelo Austria

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/

1
Trond

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: 

  1. quick_random_records kostet nur 4.6ms total.

 enter image description here

  1. die akzeptierte Antwort User.order('Rand()').limit(10) kostet 733.0ms.

 enter image description here

  1. der offset-Ansatz kostet 245.4ms insgesamt.

 enter image description here

  1. die User.all.sample(10) Annäherungskosten 573.4ms.

 enter image description here

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

  1. Model.where(id: Model.pluck(:id).sample(10)) kosten 1384.0ms

 enter image description here

  1. gem: quick_random_records kostet nur 6.4ms vollständig

 enter image description here

0
Derek Fan