Rails 3: Ottieni un record casuale

Quindi, ho trovato diversi esempi per trovare un record casuale in Rails 2 – il metodo preferito sembra essere:

Thing.find :first, :offset => rand(Thing.count) 

Essendo qualcosa di un principiante non sono sicuro di come questo possa essere costruito usando la nuova syntax di ricerca in Rails 3.

Quindi, qual è il “Rails 3 Way” per trovare un record casuale?

 Thing.first(:order => "RANDOM()") # For MySQL :order => "RAND()", - thanx, @DanSingerman # Rails 3 Thing.order("RANDOM()").first 

o

 Thing.first(:offset => rand(Thing.count)) # Rails 3 Thing.offset(rand(Thing.count)).first 

In realtà, in Rails 3 tutti gli esempi funzioneranno. Ma usando l’ordine RANDOM è piuttosto lento per i grandi tavoli ma più in stile sql

UPD. È ansible utilizzare il seguente trucco su una colonna indicizzata (syntax PostgreSQL):

 select * from my_table where id >= trunc( random() * (select max(id) from my_table) + 1 ) order by id limit 1; 

Sto lavorando a un progetto ( Rails 3.0.15, ruby ​​1.9.3-p125-perf ) in cui il db è in localhost e la tabella degli utenti ha un po ‘più di 100.000 di record .

utilizzando

ordina per RAND ()

è piuttosto lento

User.order ( “RAND (id)”). Prima

diventa

SELEZIONA users . * Dagli users ORDER BY RAND (id) LIMIT 1

e richiede da 8 a 12 secondi per rispondere !!

Registro di rotaie:

User Load (11030.8ms) SELECT users . * FROM users ORDER BY RAND () LIMIT 1

da mysql’s spiegare

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

Si può vedere che non viene utilizzato alcun indice ( possible_keys = NULL ), viene creata una tabella temporanea ed è richiesto un ulteriore passaggio per recuperare il valore desiderato ( extra = Uso temporaneo; Utilizzo di filesort ).

D’altra parte, dividendo la query in due parti e usando Ruby, abbiamo un ragionevole miglioramento nel tempo di risposta.

 users = User.scoped.select(:id);nil User.find( users.first( Random.rand( users.length )).last ) 

(; zero per l’uso in console)

Registro di rotaie:

User Load (25.2ms) SELECT id FROM users User Load (0.2 ms) SELECT users . * FROM users WHERE users . id = 106854 LIMIT 1

e mysql’s spiega come mai:

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

ora possiamo usare solo indici e la chiave primaria e fare il lavoro circa 500 volte più velocemente!

AGGIORNARE:

come sottolineato da icantbecool nei commenti, la soluzione sopra riportata presenta un difetto se nella tabella sono presenti record cancellati.

Una soluzione in questo può essere

 users_count = User.count User.scoped.limit(1).offset(rand(users_count)).first 

che si traduce in due query

 SELECT COUNT(*) FROM `users` SELECT `users`.* FROM `users` LIMIT 1 OFFSET 148794 

e corre in circa 500ms.

Se si utilizza Postgres

 User.limit(5).order("RANDOM()") 

Se si utilizza MySQL

 User.limit(5).order("RAND()") 

In entrambi i casi si selezionano 5 record casualmente dalla tabella Utenti. Ecco la query SQL effettiva visualizzata nella console.

 SELECT * FROM users ORDER BY RANDOM() LIMIT 5 

Ho creato un rails 3 gem per fare ciò che si comporta meglio su tabelle grandi e consente di concatenare relazioni e ambiti:

https://github.com/spilliton/randumb

(modifica): Il comportamento predefinito della mia gem utilizza fondamentalmente lo stesso approccio di cui sopra ora, ma hai la possibilità di usare la vecchia maniera se vuoi 🙂

Molte delle risposte pubblicate in realtà non si comportano bene su tabelle piuttosto grandi (oltre 1 milione di righe). L’ordinamento casuale richiede rapidamente alcuni secondi e anche il conteggio sul tavolo richiede tempi abbastanza lunghi.

Una soluzione che funziona bene per me in questa situazione è usare RANDOM() con una condizione where:

 Thing.where('RANDOM() >= 0.9').take 

Su una tabella con oltre un milione di righe, questa query richiede generalmente meno di 2 ms.

eccoci qui

modo di rotaie

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

uso

 Model.random #returns single random object 

o il secondo pensiero è

 module ActiveRecord class Base def self.random order("RAND()") end end end 

utilizzo:

 Model.random #returns shuffled collection 

Questo è stato molto utile per me, tuttavia avevo bisogno di un po ‘più di flessibilità, quindi questo è quello che ho fatto:

Caso 1: trovare una fonte di record casuale : il sito di trevor turk
Aggiungi questo al modello Thing.rb

 def self.random ids = connection.select_all("SELECT id FROM things") find(ids[rand(ids.length)]["id"].to_i) unless ids.blank? end 

quindi nel tuo controller puoi chiamare qualcosa di simile

 @thing = Thing.random 

Caso 2: trovare più record casuali (nessuna ripetizione) fonte: non ricordo
Avevo bisogno di trovare 10 record casuali senza ripetizioni quindi questo è quello che ho trovato funzionato
Nel tuo controller:

 thing_ids = Thing.find( :all, :select => 'id' ).map( &:id ) @things = Thing.find( (1..10).map { thing_ids.delete_at( thing_ids.size * rand ) } ) 

Questo troverà 10 record casuali, tuttavia vale la pena ricordare che se il database è particolarmente grande (milioni di record), questo non sarebbe l’ideale, e le prestazioni saranno ostacolate. Sarà in grado di eseguire fino a poche migliaia di registrazioni, sufficienti per me.

Il metodo Ruby per il prelievo casuale di un elemento da un elenco è sample . Volendo creare un sample efficiente per ActiveRecord e basato sulle risposte precedenti, ho utilizzato:

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

Lo metto in lib/ext/sample.rb e poi lo carico con questo in config/initializers/monkey_patches.rb :

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

Funziona in Rails 5 ed è agnostico DB:

Questo nel tuo controller:

 @quotes = Quote.offset(rand(Quote.count - 3)).limit(3) 

Ovviamente puoi mettere questo in una preoccupazione come mostrato qui .

app / modelli / preoccupazioni / 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 

poi…

app / modelli / book.rb

 class Book < ActiveRecord::Base include Randomable end 

Quindi puoi usare semplicemente facendo:

 Books.random 

o

 Books.random(3) 

È ansible utilizzare sample () in ActiveRecord

Per esempio

 def get_random_things_for_home_page find(:all).sample(5) end 

Fonte: http://thinkingeek.com/2011/07/04/easily-select-random-records-rails/

Se si utilizza Oracle

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

Produzione

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

Consigliamo vivamente questa gem per i record casuali, che è appositamente progettata per la tabella con molte righe di dati:

https://github.com/haopingfan/quick_random_records

Tutte le altre risposte si comportano male con un database di grandi dimensioni, tranne questa gem:

  1. quick_random_records costa solo 4.6ms totalmente.

inserisci la descrizione dell'immagine qui

  1. la risposta accettata User.order('RAND()').limit(10) costa 733.0ms .

inserisci la descrizione dell'immagine qui

  1. l’approccio offset costava in totale 245.4ms .

inserisci la descrizione dell'immagine qui

  1. l’ User.all.sample(10) costo di 573.4ms .

inserisci la descrizione dell'immagine qui

Nota: la mia tabella ha solo 120.000 utenti. Più record hai, più enorme sarà la differenza di prestazioni.


AGGIORNARE:

Esegui sul tavolo con 550.000 righe

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

inserisci la descrizione dell'immagine qui

  1. gem: quick_random_records costa solo 6.4ms totalmente

inserisci la descrizione dell'immagine qui

Un modo molto semplice per ottenere più record casuali dal tavolo. Questo rende 2 query a basso costo.

Model.where(id: Model.pluck(:id).sample(3))

Puoi cambiare il “3” al numero di record casuali che vuoi.

Mi sono solo imbattuto in questo problema nello sviluppo di una piccola applicazione in cui volevo selezionare una domanda casuale dal mio DB. Ero solito:

 @question1 = Question.where(:lesson_id => params[:lesson_id]).shuffle[1] 

E sta funzionando bene per me. Non posso parlare di come prestazioni per DB più grandi poiché questa è solo una piccola applicazione.