J'ai donc 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)
Étant un débutant, je ne sais pas comment cela pourrait être construit à l’aide de la nouvelle syntaxe de recherche de 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 utiliser order RANDOM
est assez lent 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 base de données se trouve dans localhost et la table users contient un peu plus de 100K records .
En utilisant
commander par Rand ()
est assez lent
User.order ("Rand (id)"). Premier
devient
SELECT
users
. * FROMusers
ORDER BY Rand (id) LIMIT 1
et prend de 8 à 12 secondes pour répondre !!
Journal de bord:
Charge utilisateur (11030.8ms) SELECT
users
. * FROMusers
ORDER BY Rand () LIMITE 1
de mysql expliquer
+----+-------------+-------+------+---------------+------+---------+------+--------+---------------------------------+
| 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 constater qu’aucun index n’est utilisé ( possible_keys = NULL ), une table temporaire est créée et un passe supplémentaire est requis pour extraire la valeur souhaitée ( extra = Utilisation de temporaire; Utilisation de fichier ).
D'autre part, en scindant la requête en deux parties et en utilisant Ruby, nous obtenons 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 bord:
Charge de l'utilisateur (25,2 ms) SELECT id FROM
users
Charge de l'utilisateur (0,2 ms) SELECTusers
. * FROMusers
WHEREusers
.id
= 106854 LIMITE 1
et mysql explique explique 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 rapidement!
METTRE À JOUR:
comme l'a souligné icantbecool dans les commentaires, la solution ci-dessus présente un défaut si la table contient des enregistrements supprimés.
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 court 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 le tableau Utilisateurs. Voici la requête SQL réelle affichée dans la console.
SELECT * FROM users ORDER BY RANDOM() LIMIT 5
Pour ce faire, j'ai créé un joyau de Rails 3 qui fonctionne mieux sur de grandes tables et vous permet d'enchaîner des relations et des étendues:
https://github.com/spilliton/randumb
(edit): Le comportement par défaut de ma gem utilise fondamentalement la même approche que ci-dessus, mais vous avez la possibilité d'utiliser l'ancienne méthode si vous le souhaitez :)
De nombreuses réponses publiées ne donneront pas de bons résultats sur des tables assez volumineuses (plus d'un million de lignes). Les commandes aléatoires prennent rapidement quelques secondes, et compter sur la table prend également beaucoup de temps.
Une solution qui me convient dans cette situation consiste à utiliser RANDOM()
avec la condition où:
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
#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
ou la seconde pensée est
module ActiveRecord
class Base
def self.random
order("Rand()")
end
end
end
usage:
Model.random #returns shuffled collection
C’était très utile pour moi, mais j’avais besoin d’un peu plus de souplesse. C’est ce que j’ai fait:
Cas 1: Trouver un enregistrement aléatoire source: site trevor turk
Ajouter 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: recherche de plusieurs enregistrements aléatoires (sans répétition) source: ne me souviens pas
Je devais trouver 10 enregistrements aléatoires sans répétition, c’est ce que j’ai trouvé efficace
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 base de données est particulièrement volumineuse (des millions d'enregistrements), cela ne serait pas idéal et les performances en seraient entravées. Cela fonctionnera bien jusqu'à quelques milliers de disques, ce qui était suffisant pour moi.
La méthode Ruby pour sélectionner au hasard un élément dans une liste est sample
. Voulant créer une sample
efficace pour ActiveRecord, et sur la base des 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
et le charge ensuite 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 agnostique à la base de données:
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 .
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...
class Book < ActiveRecord::Base
include Randomable
end
Ensuite, vous pouvez utiliser simplement en faisant:
Books.random
ou
Books.random(3)
Si vous utilisez Oracle
User.limit(10).order("DBMS_RANDOM.VALUE")
Sortie
SELECT * FROM users ORDER BY DBMS_RANDOM.VALUE WHERE ROWNUM <= 10
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/
Recommandez vivement cette gemme pour les enregistrements aléatoires, spécialement conçue 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 base de données volumineuse, à l'exception de cette gemme:
4.6ms
totalement.User.order('Rand()').limit(10)
a coûté 733.0ms
.offset
a coûté 245.4ms
totalement.User.all.sample(10)
coût 573.4ms
.Remarque: Ma table ne compte que 120 000 utilisateurs. Plus vous avez de disques, plus la différence de performance sera énorme.
METTRE À JOUR:
Effectuer sur une table avec 550 000 lignes
Model.where(id: Model.pluck(:id).sample(10))
coût 1384.0ms
gem: quick_random_records
ne coûte que 6.4ms
totalement