Rails 3: Obtenir un enregistrement aléatoire

Donc, j’ai trouvé plusieurs exemples pour trouver un enregistrement aléatoire dans Rails 2 – la méthode préférée semble être:

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

En tant que débutant, je ne suis pas sûr de savoir comment cela pourrait être construit en utilisant la nouvelle syntaxe find dans Rails 3.

Alors, quel est le “Rails 3 Way” pour trouver un enregistrement aléatoire?

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

ou

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

En fait, dans Rails 3, tous les exemples fonctionneront. Mais l’utilisation de la commande RANDOM est assez lente pour les grandes tables mais plus de style SQL

UPD. Vous pouvez utiliser l’astuce suivante sur une colonne indexée (syntaxe PostgreSQL):

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

Je travaille sur un projet ( Rails 3.0.15, ruby ​​1.9.3-p125-perf ) où la firebase database se trouve dans localhost et où la table users contient un peu plus de 100 000 enregistrements .

En utilisant

commande par RAND ()

est assez lent

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

devient

SELECT users . * FROM users LIMITE DE COMMANDE PAR RAND (id) 1

et prend de 8 à 12 secondes pour répondre !!

Journal de rails:

Charge utilisateur (11030.8ms) SELECT users . * FROM users ORDER BY RAND () LIMIT 1

de mysql expliquent

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

Vous pouvez voir qu’aucun index n’est utilisé ( possible_keys = NULL ), une table temporaire est créée et une passe supplémentaire est nécessaire pour récupérer la valeur souhaitée ( extra = Utilisation temporaire; Utilisation du port de fichiers ).

En revanche, en divisant la requête en deux parties et en utilisant Ruby, nous avons une amélioration raisonnable du temps de réponse.

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

(; Nil pour l’utilisation de la console)

Journal de rails:

Charge utilisateur (25.2ms) SELECT ID FROM users Charge utilisateur (0.2ms) SELECT users . * FROM users WHERE users . id = 106854 LIMIT 1

et l’explication de mysql prouve pourquoi:

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

nous pouvons maintenant utiliser uniquement les index et la clé primaire et faire le travail environ 500 fois plus vite!

METTRE À JOUR:

Comme le fait remarquer icantbecool dans les commentaires, la solution ci-dessus présente une faille s’il ya des enregistrements supprimés dans la table.

Une solution de contournement peut être

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

qui se traduit par deux requêtes

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

et fonctionne dans environ 500ms.

Si vous utilisez Postgres

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

Si vous utilisez MySQL

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

Dans les deux cas, vous sélectionnez 5 enregistrements au hasard dans la table Users. Voici la requête SQL réelle affichée dans la console.

 SELECT * FROM users ORDER BY RANDOM() LIMIT 5 

J’ai fait un rail 3 gem pour faire cela qui fonctionne mieux sur de grandes tables et vous permet d’enchaîner les relations et les scopes:

https://github.com/spilliton/randumb

(edit): Le comportement par défaut de ma gemme utilise essentiellement la même approche que ci-dessus, mais vous avez la possibilité d’utiliser l’ancienne méthode si vous le souhaitez 🙂

La plupart des réponses publiées ne donnent pas de bons résultats sur des tables assez grandes (1 million de lignes). Le classement aléatoire prend rapidement quelques secondes et le décompte sur la table prend également beaucoup de temps.

Une solution qui fonctionne bien pour moi dans cette situation est d’utiliser RANDOM() avec une condition where:

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

Sur une table de plus d’un million de lignes, cette requête prend généralement moins de 2 ms.

Et c’est parti

chemin de rails

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

usage

 Model.random #returns single random object 

ou la deuxième pensée est

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

usage:

 Model.random #returns shuffled collection 

Cela m’a été très utile, mais j’avais besoin d’un peu plus de flexibilité, alors c’est ce que j’ai fait:

Cas 1: Trouver une source d’ enregistrement aléatoire : site de trevor turk
Ajoutez ceci au modèle 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 

alors dans votre contrôleur, vous pouvez appeler quelque chose comme ça

 @thing = Thing.random 

Cas 2: Trouver plusieurs enregistrements aléatoires (pas de répétitions) source: je ne me souviens plus
Je devais trouver 10 enregistrements aléatoires sans répétitions donc c’est ce que j’ai trouvé travaillé
Dans votre contrôleur:

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

Cela trouvera 10 enregistrements aléatoires, mais il convient de mentionner que si la firebase database est particulièrement volumineuse (des millions d’enregistrements), cela ne serait pas idéal et les performances seraient entravées. Est performera jusqu’à quelques milliers de disques ce qui me suffisait.

La méthode Ruby pour sélectionner aléatoirement un élément d’une liste est un sample . Voulant créer un sample efficace pour ActiveRecord, et basé sur les réponses précédentes, j’ai utilisé:

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

Je mets ceci dans lib/ext/sample.rb puis le charge avec ceci dans config/initializers/monkey_patches.rb :

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

Fonctionne dans Rails 5 et est indépendant de la firebase database:

Ceci dans votre contrôleur:

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

Vous pouvez, bien sûr, mettre cela dans une préoccupation, comme indiqué ici .

app / modèles / soucis / 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 

puis…

app / models / book.rb

 class Book < ActiveRecord::Base include Randomable end 

Ensuite, vous pouvez utiliser simplement en faisant:

 Books.random 

ou

 Books.random(3) 

Vous pouvez utiliser sample () dans ActiveRecord

Par exemple

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

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

Si vous utilisez Oracle

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

Sortie

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

Fortement Recommander ce bijou pour les enregistrements aléatoires, qui est spécialement conçu pour les tables avec beaucoup de lignes de données:

https://github.com/haopingfan/quick_random_records

Toutes les autres réponses fonctionnent mal avec une grande firebase database, à l’exception de ce joyau:

  1. quick_random_records ne coute que 4.6ms .

entrer la description de l'image ici

  1. la réponse acceptée User.order('RAND()').limit(10) coûte 733.0ms .

entrer la description de l'image ici

  1. L’approche offset coûté 245.4ms .

entrer la description de l'image ici

  1. L’ User.all.sample(10) coûte 573.4ms .

entrer la description de l'image ici

Remarque: Ma table ne contient que 120 000 utilisateurs. Plus vous avez de disques, plus la différence de performance sera énorme.


METTRE À JOUR:

Effectuer sur table avec 550 000 lignes

  1. Model.where(id: Model.pluck(:id).sample(10)) coûte 1384.0ms

entrer la description de l'image ici

  1. gem: quick_random_records ne coûte que 6.4ms

entrer la description de l'image ici

Un moyen très facile d’obtenir plusieurs enregistrements aléatoires à partir de la table. Cela fait 2 requêtes bon marché.

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

Vous pouvez changer le “3” pour le nombre d’enregistrements aléatoires que vous voulez.

Je viens juste de rencontrer ce problème en développant une petite application où je voulais sélectionner une question aléatoire de ma firebase database. J’ai utilisé:

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

Et ça marche bien pour moi. Je ne peux pas parler de la performance des plus grandes bases de données car il ne s’agit que d’une petite application.