Enregistrement aléatoire dans ActiveRecord

151

J'ai besoin d'obtenir un enregistrement aléatoire d'une table via ActiveRecord. J'ai suivi l'exemple de Jamis Buck de 2006 .

Cependant, j'ai également rencontré un autre moyen via une recherche Google (impossible d'attribuer un lien en raison de nouvelles restrictions utilisateur):

 rand_id = rand(Model.count)
 rand_record = Model.first(:conditions => ["id >= ?", rand_id])

Je suis curieux de savoir comment les autres ici l'ont fait ou si quelqu'un sait de quelle manière serait plus efficace.

Jyunderwood
la source
2
2 points qui pourraient aider à répondre. 1. Dans quelle mesure vos identifiants sont-ils uniformément répartis, sont-ils séquentiels? 2. Dans quelle mesure cela doit-il être aléatoire? Assez bon aléatoire ou réel aléatoire?
Michael
Ce sont des identifiants séquentiels générés automatiquement par activerecord et il suffit que ce soit assez bon.
jyunderwood
1
Ensuite, votre solution proposée est proche de l'idéal :) J'utiliserais "SELECT MAX (id) FROM table_name" au lieu du COUNT (*) car il traitera un peu mieux les lignes supprimées, sinon, le reste va bien. En bref, si «assez bien» est correct, alors vous devez juste avoir une méthode qui suppose une distribution proche de ce que vous avez réellement. Si c'est uniforme et même comme vous l'avez dit, le rand simple fonctionne très bien.
Michael
1
Cela ne fonctionnera pas lorsque vous aurez supprimé des lignes.
Venkat D.

Réponses:

136

Je n'ai pas trouvé le moyen idéal de le faire sans au moins deux requêtes.

Ce qui suit utilise un nombre généré aléatoirement (jusqu'au nombre d'enregistrements actuel) comme décalage .

offset = rand(Model.count)

# Rails 4
rand_record = Model.offset(offset).first

# Rails 3
rand_record = Model.first(:offset => offset)

Pour être honnête, je viens d'utiliser ORDER BY RAND () ou RANDOM () (selon la base de données). Ce n'est pas un problème de performances si vous n'avez pas de problème de performances.

Toby Hede
la source
2
Le code Model.find(:offset => offset).firstlancera une erreur. Je pense que cela Model.first(:offset => offset)pourrait mieux fonctionner.
Harish Shetty
1
ouais, je travaille avec Rails 3 et je suis toujours confus sur les formats de requête entre les versions.
Toby Hede
7
Notez que l'utilisation de l'offset est très lente avec un ensemble de données volumineux, car elle nécessite en fait une analyse d'index (ou une analyse de table, au cas où un index clusterisé serait utilisé comme InnoDB). En d'autres termes, c'est une opération O (N) mais "WHERE id> = # {rand_id} ORDER BY id ASC LIMIT 1" est O (log N), ce qui est beaucoup plus rapide.
kenn
15
Sachez que l'approche offset ne produit qu'un seul point de données trouvé aléatoirement (le premier, tous après sont toujours triés par id). Si vous avez besoin de plusieurs enregistrements sélectionnés au hasard, vous devez utiliser cette approche plusieurs fois ou utiliser la méthode de classement aléatoire fournie par votre base de données, c'est- Thing.order("RANDOM()").limit(100)à- dire pour 100 entrées sélectionnées au hasard. (Sachez que c'est RANDOM()dans PostgreSQL et RAND()MySQL ... pas aussi portable que vous le souhaitez.)
Florian Pilz
3
Ne fonctionne pas pour moi sur Rails 4. Utilisation Model.offset(offset).first.
mahemoff
206

Rails 6

Comme indiqué par Jason dans les commentaires, dans Rails 6, les arguments sans attribut ne sont pas autorisés. Vous devez encapsuler la valeur dans une Arel.sql()instruction.

Model.order(Arel.sql('RANDOM()')).first

Rails 5, 4

Dans Rails 4 et 5 , en utilisant Postgresql ou SQLite , en utilisant RANDOM():

Model.order('RANDOM()').first

Vraisemblablement, la même chose fonctionnerait pour MySQL avecRAND()

Model.order('RAND()').first

C'est environ 2,5 fois plus rapide que l'approche de la réponse acceptée .

Attention : cela est lent pour les grands ensembles de données avec des millions d'enregistrements, vous pouvez donc ajouter une limitclause.

Mohamad
la source
4
"Random ()" fonctionne également dans sqlite, donc pour ceux d'entre nous qui développent encore sur sqlite et exécutent postgres en production, votre solution fonctionne dans les deux environnements.
wuliwong
5
J'ai créé une référence pour cela par rapport à la réponse acceptée. Sur PostgreSQL 9.4, l'approche de cette réponse est environ deux fois plus rapide.
panmari
3
On dirait que cela n'est pas recommandé sur mysql webtrenches.com/post.cfm/avoid-rand-in-mysql
Prakash Murthy
C'est la solution la plus rapide
Sergio Belevskij
1
"Les arguments sans attribut ne seront pas autorisés dans Rails 6.0. Cette méthode ne doit pas être appelée avec des valeurs fournies par l'utilisateur, telles que des paramètres de requête ou des attributs de modèle. Les valeurs connues de sécurité peuvent être transmises en les enveloppant dans Arel.sql ()."
Trenton Tyler
73

Votre exemple de code commencera à se comporter de manière inexacte une fois les enregistrements supprimés (il favorisera injustement les éléments avec des identifiants inférieurs)

Vous feriez probablement mieux d'utiliser les méthodes aléatoires de votre base de données. Celles-ci varient en fonction de la base de données que vous utilisez, mais: order => "RAND ()" fonctionne pour mysql et: order => "RANDOM ()" fonctionne pour postgres

Model.first(:order => "RANDOM()") # postgres example
art sémantique
la source
7
ORDER BY RAND () pour MySQL se termine par une exécution horrible à mesure que les données augmentent. Il est impossible à maintenir (selon les exigences de temps) même à partir de quelques milliers de lignes.
Michael
Michael soulève un excellent point (c'est également vrai pour les autres bases de données). En général, la sélection de lignes aléatoires à partir de grandes tables n'est pas quelque chose que vous voulez faire dans une action dynamique. La mise en cache est votre ami. Repenser ce que vous essayez d'accomplir n'est peut-être pas non plus une mauvaise idée.
semanticart
1
Trier RAND () dans mysql sur une table avec environ un million de lignes est slooooooooooooooooooooow.
Subimage
24
Ça ne marche plus. Utilisez Model.order("RANDOM()").firstplutôt.
phil pirozhkov
Lent et spécifique à la base de données. ActiveRecord est censé fonctionner de manière transparente entre les bases de données, vous ne devez donc pas utiliser cette méthode.
Dex
29

Benchmarking de ces deux méthodes sur MySQL 5.1.49, Ruby 1.9.2p180 sur une table de produits avec + 5 millions d'enregistrements:

def random1
  rand_id = rand(Product.count)
  rand_record = Product.first(:conditions => [ "id >= ?", rand_id])
end

def random2
  if (c = Product.count) != 0
    Product.find(:first, :offset =>rand(c))
  end
end

n = 10
Benchmark.bm(7) do |x|
  x.report("next id:") { n.times {|i| random1 } }
  x.report("offset:")  { n.times {|i| random2 } }
end


             user     system      total        real
next id:  0.040000   0.000000   0.040000 (  0.225149)
offset :  0.020000   0.000000   0.020000 ( 35.234383)

Le décalage dans MySQL semble être beaucoup plus lent.

EDIT j'ai aussi essayé

Product.first(:order => "RAND()")

Mais j'ai dû le tuer après ~ 60 secondes. MySQL était "Copier vers la table tmp sur le disque". Cela ne fonctionnera pas.

dkam
la source
1
Pour ceux qui recherchent plus de tests, combien de temps prend une vraie approche aléatoire: j'ai essayé Thing.order("RANDOM()").firstsur une table avec 250k entrées - la requête s'est terminée en moins d'une demi-seconde. (PostgreSQL 9.0, REE 1.8.7, 2 cœurs de 2,66 GHz) C'est assez rapide pour moi, puisque je fais un "nettoyage" ponctuel.
Florian Pilz le
6
La méthode rand de Ruby renvoie un de moins que le nombre spécifié, donc vous voudrez rand_id = rand(Product.count) + 1ou vous n'obtiendrez jamais le dernier enregistrement.
Ritchie
4
Remarque random1ne fonctionnera pas si vous supprimez une ligne dans le tableau. (Le nombre sera inférieur à l'ID maximum et vous ne pourrez jamais sélectionner des lignes avec des ID élevés).
Nicholas
L'utilisation random2peut être améliorée en #orderutilisant une colonne indexée.
Carson Reinke
18

Cela n'a pas à être si difficile.

ids = Model.pluck(:id)
random_model = Model.find(ids.sample)

pluckrenvoie un tableau de tous les identifiants de la table. La sampleméthode sur le tableau, retourne un identifiant aléatoire du tableau.

Cela devrait bien fonctionner, avec une probabilité égale de sélection et de prise en charge des tables avec des lignes supprimées. Vous pouvez même le mélanger avec des contraintes.

User.where(favorite_day: "Friday").pluck(:id)

Et choisissez ainsi un utilisateur aléatoire qui aime les vendredis plutôt que n'importe quel utilisateur.

Niels B.
la source
8
Ceci est propre et fonctionne pour une petite table ou une utilisation unique, notez simplement qu'il ne sera pas mis à l'échelle. Sur une table 3M, cueillir des identifiants prend environ 15 secondes pour moi sur MariaDB.
mahemoff
2
C'est un bon point. Avez-vous trouvé une solution alternative plus rapide, tout en conservant les mêmes qualités?
Niels B.
La solution offset acceptée ne conserve-t-elle pas les mêmes qualités?
mahemoff
Non, il ne prend pas en charge les conditions et n'a pas une probabilité égale de sélection pour les tables avec des enregistrements supprimés.
Niels B.
1
À bien y penser, si vous appliquez les contraintes lors du comptage et de la sélection avec un décalage, la technique devrait fonctionner. J'imaginais seulement l'appliquer sur le compte.
Niels B.
15

Il n'est pas conseillé d'utiliser cette solution, mais si pour une raison quelconque vous voulez vraiment sélectionner au hasard un enregistrement tout en ne faisant qu'une seule requête de base de données, vous pouvez utiliser la sampleméthode de la classe Ruby Array , qui vous permet de sélectionner un élément aléatoire à partir d'un tableau.

Model.all.sample

Cette méthode ne nécessite qu'une requête de base de données, mais elle est beaucoup plus lente que les alternatives comme celles Model.offset(rand(Model.count)).firstqui nécessitent deux requêtes de base de données, bien que cette dernière soit toujours préférée.

Ryan Atallah
la source
99
Ne faites pas cela. Déjà.
Zabba
5
Si vous avez 100 000 lignes dans votre base de données, toutes devront être chargées en mémoire.
Venkat D.
3
Bien sûr, ce n'est pas recommandé pour le code en temps réel de production, mais j'aime cette solution, elle est très claire à utiliser pour des situations particulières comme l' ensemencement de la base de données avec de fausses valeurs.
fguillen
13
S'il vous plaît - ne dites jamais jamais. C'est une excellente solution pour le débogage au moment du développement si la table est petite. (Et si vous prenez des échantillons, le débogage est probablement le cas d'utilisation).
mahemoff
J'utilise pour l'ensemencement et c'est bon pour moi. De plus, Model.all.sample (n) fonctionne aussi :)
Arnaldo Ignacio Gaspar Véjar
13

J'ai fait un joyau de rails 3 pour gérer cela:

https://github.com/spilliton/randumb

Cela vous permet de faire des choses comme ceci:

Model.where(:column => "value").random(10)
déversement
la source
7
Dans la documentation de ce joyau, ils expliquent que "randumb tacks simplement un supplément ORDER BY RANDOM()(ou RAND()pour mysql) à votre requête." - par conséquent, les commentaires sur les mauvaises performances mentionnés dans les commentaires à la réponse de @semanticart s'appliquent également lors de l'utilisation de cette gemme. Mais au moins, c'est indépendant de la DB.
Nicolas
8

Je l'utilise si souvent depuis la console que j'étends ActiveRecord dans un initialiseur - exemple Rails 4:

class ActiveRecord::Base
  def self.random
    self.limit(1).offset(rand(self.count)).first
  end
end

Je peux alors appeler Foo.randompour ramener un enregistrement aléatoire.

Knotty66
la source
1
avez-vous besoin limit(1)? ActiveRecord#firstdevrait être assez intelligent pour le faire.
tokland
6

Une requête dans Postgres:

User.order('RANDOM()').limit(3).to_sql # Postgres example
=> "SELECT "users".* FROM "users" ORDER BY RANDOM() LIMIT 3"

À l'aide d'un décalage, deux requêtes:

offset = rand(User.count) # returns an integer between 0 and (User.count - 1)
Model.offset(offset).limit(1)
Thomas Klemm
la source
1
Pas besoin de -1, rand compte jusqu'à num - 1
anemaria20
Merci, modifié: +1:
Thomas Klemm
5

La lecture de tous ces éléments ne m'a pas donné beaucoup de confiance pour savoir lequel d'entre eux fonctionnerait le mieux dans ma situation particulière avec Rails 5 et MySQL / Maria 5.5. J'ai donc testé certaines des réponses sur ~ 65000 enregistrements et j'ai deux choses à retenir:

  1. RAND () avec un limitest un gagnant clair.
  2. N'utilisez pas pluck+ sample.
def random1
  Model.find(rand((Model.last.id + 1)))
end

def random2
  Model.order("RAND()").limit(1)
end

def random3
  Model.pluck(:id).sample
end

n = 100
Benchmark.bm(7) do |x|
  x.report("find:")    { n.times {|i| random1 } }
  x.report("order:")   { n.times {|i| random2 } }
  x.report("pluck:")   { n.times {|i| random3 } }
end

              user     system      total        real
find:     0.090000   0.000000   0.090000 (  0.127585)
order:    0.000000   0.000000   0.000000 (  0.002095)
pluck:    6.150000   0.000000   6.150000 (  8.292074)

Cette réponse synthétise, valide et met à jour la réponse de Mohamed , ainsi que le commentaire de Nami WANG sur la même chose et le commentaire de Florian Pilz sur la réponse acceptée - veuillez leur envoyer des votes!

Sam
la source
3

Vous pouvez utiliser la Arrayméthode sample, la méthode sampleretourne un objet aléatoire à partir d'un tableau, pour l'utiliser, il vous suffit d'exécuter une ActiveRecordrequête simple qui retourne une collection, par exemple:

User.all.sample

retournera quelque chose comme ceci:

#<User id: 25, name: "John Doe", email: "[email protected]", created_at: "2018-04-16 19:31:12", updated_at: "2018-04-16 19:31:12">
trejo08
la source
Je ne recommanderais pas de travailler avec des méthodes de tableau tout en utilisant AR. Cette méthode prend presque 8 fois le temps de order('rand()').limit(1)faire "le même" travail (avec ~ 10K enregistrements).
Sebastian Palma
3

Je recommande vivement ce joyau pour les enregistrements aléatoires, 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 base de données, à l'exception de ce joyau:

  1. quick_random_records ne coûte que 4.6mstotalement.

entrez la description de l'image ici

  1. le User.order('RAND()').limit(10)coût 733.0ms.

entrez la description de l'image ici

  1. l' offsetapproche de réponse acceptée coûte 245.4mstotalement.

entrez la description de l'image ici

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

entrez la description de l'image ici


Remarque: ma table ne compte que 120 000 utilisateurs. Plus vous avez d'enregistrements, plus la différence de performances sera énorme.

Fan de Derek
la source
2

Si vous devez sélectionner des résultats aléatoires dans la portée spécifiée :

scope :male_names, -> { where(sex: 'm') }
number_of_results = 10

rand = Names.male_names.pluck(:id).sample(number_of_results)
Names.where(id: rand)
Yuri Karpovich
la source
1

La méthode Ruby pour choisir au hasard un élément dans une liste est sample. Voulant créer un efficace samplepour 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 lib/ext/sample.rbet puis le charge avec ceci dans config/initializers/monkey_patches.rb:

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

Ce sera une requête si la taille du modèle est déjà mise en cache et deux dans le cas contraire.

Dan Kohn
la source
1

Rails 4.2 et Oracle :

Pour oracle, vous pouvez définir une portée sur votre modèle comme ceci:

scope :random_order, -> {order('DBMS_RANDOM.RANDOM')}

ou

scope :random_order, -> {order('DBMS_RANDOM.VALUE')}

Et puis pour un échantillon, appelez-le comme ceci:

Model.random_order.take(10)

ou

Model.random_order.limit(5)

bien sûr, vous pouvez également passer une commande sans portée comme celle-ci:

Model.all.order('DBMS_RANDOM.RANDOM') # or DBMS_RANDOM.VALUE respectively
mahatmanich
la source
Vous pouvez également le faire avec postgres avec order('random()'et MySQL avec order('rand()'). C'est certainement la meilleure réponse.
jrochkind
1

Pour la base de données MySQL, essayez: Model.order ("RAND ()"). First

Vadim Eremeev
la source
Cela ne fonctionne pas sur mysql .. vous devriez inclure au moins avec quel moteur DB est supposé fonctionner
Arnold Roa
Désolé, il y a eu une faute de frappe. Corrigé maintenant. Devrait fonctionner pour mysql (uniquement)
Vadim Eremeev
1

Si vous utilisez PostgreSQL 9.5+, vous pouvez profiter de TABLESAMPLE pour sélectionner un enregistrement aléatoire.

Les deux méthodes d'échantillonnage par défaut ( SYSTEMetBERNOULLI ) nécessitent que vous spécifiiez le nombre de lignes à renvoyer sous forme de pourcentage du nombre total de lignes de la table.

-- Fetch 10% of the rows in the customers table.
SELECT * FROM customers TABLESAMPLE BERNOULLI(10);

Cela nécessite de connaître la quantité d'enregistrements dans le tableau pour sélectionner le pourcentage approprié, qui peut ne pas être facile à trouver rapidement. Heureusement, il y a le tsm_system_rowsmodule qui permet de spécifier le nombre de lignes à renvoyer directement.

CREATE EXTENSION tsm_system_rows;

-- Fetch a single row from the customers table.
SELECT * FROM customers TABLESAMPLE SYSTEM_ROWS(1);

Pour l'utiliser dans ActiveRecord, activez d'abord l'extension dans une migration:

class EnableTsmSystemRowsExtension < ActiveRecord::Migration[5.0]
  def change
    enable_extension "tsm_system_rows"
  end
end

Puis modifiez le from clause de la requête:

customer = Customer.from("customers TABLESAMPLE SYSTEM_ROWS(1)").first

Je ne sais pas si la SYSTEM_ROWSméthode d'échantillonnage sera entièrement aléatoire ou si elle renvoie simplement la première ligne d'une page aléatoire.

La plupart de ces informations proviennent d'un article de blog 2ndQuadrant rédigé par Gulcin Yildirim .

Adam Sheehan
la source
1

Après avoir vu autant de réponses, j'ai décidé de les comparer toutes sur ma base de données PostgreSQL (9.6.3). J'utilise une table plus petite de 100 000 et je me suis débarrassé du Model.order ("RANDOM ()"). D'abord car il était déjà deux ordres de grandeur plus lent.

En utilisant une table avec 2500000 entrées avec 10 colonnes, le vainqueur a été la méthode de pluck presque 8 fois plus rapide que le finaliste (offset. Je l'ai uniquement exécuté sur un serveur local afin que ce nombre puisse être gonflé, mais il est suffisamment grand pour que le pluck C'est ce que je vais finir par utiliser. Il est également intéressant de noter que cela pourrait causer des problèmes si vous tirez plus d'un résultat à la fois car chacun d'entre eux sera unique, c'est-à-dire moins aléatoire.

Pluck gagne 100 fois sur ma table de 25 000 000 lignes Edit: en fait, cette fois inclut le pluck dans la boucle si je le retire, il fonctionne à peu près aussi vite qu'une simple itération sur l'id. Toutefois; cela prend une bonne quantité de RAM.

RandomModel                 user     system      total        real
Model.find_by(id: i)       0.050000   0.010000   0.060000 (  0.059878)
Model.offset(rand(offset)) 0.030000   0.000000   0.030000 ( 55.282410)
Model.find(ids.sample)     6.450000   0.050000   6.500000 (  7.902458)

Voici les données exécutées 2000 fois sur ma table de 100000 lignes pour exclure le hasard

RandomModel       user     system      total        real
find_by:iterate  0.010000   0.000000   0.010000 (  0.006973)
offset           0.000000   0.000000   0.000000 (  0.132614)
"RANDOM()"       0.000000   0.000000   0.000000 ( 24.645371)
pluck            0.110000   0.020000   0.130000 (  0.175932)
Mendoza
la source
1

Très vieille question mais avec:

rand_record = Model.all.shuffle

Vous avez un tableau d'enregistrement, trié par ordre aléatoire. Pas besoin de gemmes ou de scripts.

Si vous voulez un enregistrement:

rand_record = Model.all.shuffle.first
Gregdebrick
la source
1
Ce n'est pas la meilleure option, car cela charge tous les enregistrements en mémoire. Aussi, shuffle.first==.sample
Andrew Rozhenko
0

Je suis tout nouveau sur RoR mais j'ai obtenu que cela fonctionne pour moi:

 def random
    @cards = Card.all.sort_by { rand }
 end

C'est venu de:

Comment trier (brouiller) au hasard un tableau dans Ruby?

Aaron Pennington
la source
4
Le problème, c'est qu'il va charger toutes les cartes de la base de données. Il est plus efficace de le faire dans la base de données.
Anton Kuzmin
Vous pouvez également mélanger des tableaux avec array.shuffle. Quoi qu'il en soit, méfiez-vous, car Card.allcela chargera tous les enregistrements de la carte en mémoire, ce qui devient plus inefficace avec le nombre d'objets dont nous parlons.
Thomas Klemm
0

Que faire:

rand_record = Model.find(Model.pluck(:id).sample)

Pour moi c'est bien clair

poramo
la source
0

J'essaie l'exemple de Sam sur mon application en utilisant les rails 4.2.8 de Benchmark (je mets 1..Category.count pour aléatoire, car si le hasard prend un 0, il produira une erreur (ActiveRecord :: RecordNotFound: Could not find Catégorie avec 'id' = 0)) et le mien était:

 def random1
2.4.1 :071?>   Category.find(rand(1..Category.count))
2.4.1 :072?>   end
 => :random1
2.4.1 :073 > def random2
2.4.1 :074?>    Category.offset(rand(1..Category.count))
2.4.1 :075?>   end
 => :random2
2.4.1 :076 > def random3
2.4.1 :077?>   Category.offset(rand(1..Category.count)).limit(rand(1..3))
2.4.1 :078?>   end
 => :random3
2.4.1 :079 > def random4
2.4.1 :080?>    Category.pluck(rand(1..Category.count))
2.4.1 :081?>
2.4.1 :082 >     end
 => :random4
2.4.1 :083 > n = 100
 => 100
2.4.1 :084 > Benchmark.bm(7) do |x|
2.4.1 :085 >     x.report("find") { n.times {|i| random1 } }
2.4.1 :086?>   x.report("offset") { n.times {|i| random2 } }
2.4.1 :087?>   x.report("offset_limit") { n.times {|i| random3 } }
2.4.1 :088?>   x.report("pluck") { n.times {|i| random4 } }
2.4.1 :089?>   end

                  user      system      total     real
find            0.070000   0.010000   0.080000 (0.118553)
offset          0.040000   0.010000   0.050000 (0.059276)
offset_limit    0.050000   0.000000   0.050000 (0.060849)
pluck           0.070000   0.020000   0.090000 (0.099065)
rld
la source
0

.order('RANDOM()').limit(limit)semble soigné mais est lent pour les grandes tables car il doit récupérer et trier toutes les lignes même si limitest 1 (en interne dans la base de données mais pas dans Rails). Je ne suis pas sûr de MySQL mais cela se produit dans Postgres. Plus d'explications ici et ici .

Une solution pour les grandes tables est .from("products TABLESAMPLE SYSTEM(0.5)")où les 0.5moyens 0.5%. Cependant, je trouve que cette solution est encore lente si vous avez des WHEREconditions qui filtrent beaucoup de lignes. Je suppose que c'est parce que TABLESAMPLE SYSTEM(0.5)récupérez toutes les lignes avant que les WHEREconditions ne s'appliquent.

Une autre solution pour les grandes tables (mais pas très aléatoires) est:

products_scope.limit(sample_size).sample(limit)

sample_sizepeut être 100(mais pas trop grand sinon c'est lent et consomme beaucoup de mémoire), et limitpeut être 1. Notez que bien que ce soit rapide mais pas vraiment aléatoire, il est aléatoire sample_sizeuniquement dans les enregistrements.

PS: Les résultats de référence dans les réponses ci-dessus ne sont pas fiables (du moins dans Postgres) car certaines requêtes DB exécutées à la deuxième fois peuvent être beaucoup plus rapides que celles exécutées à la première fois, grâce au cache DB. Et malheureusement, il n'y a pas de moyen simple de désactiver le cache dans Postgres pour rendre ces benchmarks fiables.

Barrage de Linh
la source
0

En plus de l'utilisation RANDOM(), vous pouvez également jeter ceci dans une portée:

class Thing
  scope :random, -> (limit = 1) {
    order('RANDOM()').
    limit(limit)
  }
end

Ou, si vous n'aimez pas cela comme une portée, jetez-la simplement dans une méthode de classe. Fonctionne maintenant Thing.randomavec Thing.random(n).

Damien Roche
la source
0

En fonction de la signification du mot «aléatoire» et de ce que vous voulez réellement faire, take cela peut suffire.

Par «signification» de aléatoire, je veux dire:

  • Voulez-vous dire, donnez-moi un élément dont je me fiche de sa position? alors c'est assez.
  • Maintenant, si vous voulez dire "donnez-moi n'importe quel élément avec une bonne probabilité que des expériences répétées me donneront des éléments différents de l'ensemble" alors, forcez la "chance" avec l'une des méthodes mentionnées dans les autres réponses.

Par exemple, pour les tests, des échantillons de données auraient pu être créés au hasard de toute façon, c'est donc takeplus que suffisant, et pour être honnête, même first.

https://guides.rubyonrails.org/active_record_querying.html#take

jgomo3
la source