web-dev-qa-db-fra.com

Rails 3: Obtenir un enregistrement aléatoire

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?

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

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

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

Journal de bord:

Charge utilisateur (11030.8ms) SELECT users. * FROM users 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) SELECT users. * FROM users WHERE users.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.

29
xlembouras

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

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

11
spilliton

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.

6
fivedigit

et c'est parti

Voie ferrée

#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 seconde pensée est

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

usage:

Model.random #returns shuffled collection
5
huan son

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.

4
Hishalv

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

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 .

app/modèles/préoccupations/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)
3
richardun

Si vous utilisez Oracle

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

Sortie

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

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/

1
Trond

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: 

  1. quick_random_records n'a coûté que 4.6ms totalement.

 enter image description here

  1. la réponse acceptée User.order('Rand()').limit(10) a coûté 733.0ms.

 enter image description here

  1. l'approche offset a coûté 245.4ms totalement.

 enter image description here

  1. l'approche User.all.sample(10) coût 573.4ms.

 enter image description here

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

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

 enter image description here

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

 enter image description here

0
Derek Fan