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.
ruby-on-rails
random
rails-activerecord
Jyunderwood
la source
la source
Réponses:
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 .
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.
la source
Model.find(:offset => offset).first
lancera une erreur. Je pense que celaModel.first(:offset => offset)
pourrait mieux fonctionner.Thing.order("RANDOM()").limit(100)
à- dire pour 100 entrées sélectionnées au hasard. (Sachez que c'estRANDOM()
dans PostgreSQL etRAND()
MySQL ... pas aussi portable que vous le souhaitez.)Model.offset(offset).first
.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.Rails 5, 4
Dans Rails 4 et 5 , en utilisant Postgresql ou SQLite , en utilisant
RANDOM()
:Vraisemblablement, la même chose fonctionnerait pour MySQL avec
RAND()
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
limit
clause.la source
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
la source
Model.order("RANDOM()").first
plutôt.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:
Le décalage dans MySQL semble être beaucoup plus lent.
EDIT j'ai aussi essayé
Mais j'ai dû le tuer après ~ 60 secondes. MySQL était "Copier vers la table tmp sur le disque". Cela ne fonctionnera pas.
la source
Thing.order("RANDOM()").first
sur 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.rand_id = rand(Product.count) + 1
ou vous n'obtiendrez jamais le dernier enregistrement.random1
ne 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).random2
peut être améliorée en#order
utilisant une colonne indexée.Cela n'a pas à être si difficile.
pluck
renvoie un tableau de tous les identifiants de la table. Lasample
mé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.
Et choisissez ainsi un utilisateur aléatoire qui aime les vendredis plutôt que n'importe quel utilisateur.
la source
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
sample
méthode de la classe Ruby Array , qui vous permet de sélectionner un élément aléatoire à partir d'un tableau.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)).first
qui nécessitent deux requêtes de base de données, bien que cette dernière soit toujours préférée.la source
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:
la source
ORDER BY RANDOM()
(ouRAND()
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.Je l'utilise si souvent depuis la console que j'étends ActiveRecord dans un initialiseur - exemple Rails 4:
Je peux alors appeler
Foo.random
pour ramener un enregistrement aléatoire.la source
limit(1)
?ActiveRecord#first
devrait être assez intelligent pour le faire.Une requête dans Postgres:
À l'aide d'un décalage, deux requêtes:
la source
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:
limit
est un gagnant clair.pluck
+sample
.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!
la source
Vous pouvez utiliser la
Array
méthodesample
, la méthodesample
retourne un objet aléatoire à partir d'un tableau, pour l'utiliser, il vous suffit d'exécuter uneActiveRecord
requête simple qui retourne une collection, par exemple:retournera quelque chose comme ceci:
la source
order('rand()').limit(1)
faire "le même" travail (avec ~ 10K enregistrements).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:
4.6ms
totalement.User.order('RAND()').limit(10)
coût733.0ms
.offset
approche de réponse acceptée coûte245.4ms
totalement.User.all.sample(10)
coût d'approche573.4ms
.Remarque: ma table ne compte que 120 000 utilisateurs. Plus vous avez d'enregistrements, plus la différence de performances sera énorme.
la source
Si vous devez sélectionner des résultats aléatoires dans la portée spécifiée :
la source
La méthode Ruby pour choisir au hasard un élément dans une liste est
sample
. Voulant créer un efficacesample
pour ActiveRecord, et sur la base des réponses précédentes, j'ai utilisé:Je mets ceci
lib/ext/sample.rb
et puis le charge avec ceci dansconfig/initializers/monkey_patches.rb
:Ce sera une requête si la taille du modèle est déjà mise en cache et deux dans le cas contraire.
la source
Rails 4.2 et Oracle :
Pour oracle, vous pouvez définir une portée sur votre modèle comme ceci:
ou
Et puis pour un échantillon, appelez-le comme ceci:
ou
bien sûr, vous pouvez également passer une commande sans portée comme celle-ci:
la source
order('random()'
et MySQL avecorder('rand()')
. C'est certainement la meilleure réponse.Pour la base de données MySQL, essayez: Model.order ("RAND ()"). First
la source
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 (
SYSTEM
etBERNOULLI
) nécessitent que vous spécifiiez le nombre de lignes à renvoyer sous forme de pourcentage du nombre total de lignes de la table.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_rows
module qui permet de spécifier le nombre de lignes à renvoyer directement.Pour l'utiliser dans ActiveRecord, activez d'abord l'extension dans une migration:
Puis modifiez le
from
clause de la requête:Je ne sais pas si la
SYSTEM_ROWS
mé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 .
la source
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.
Voici les données exécutées 2000 fois sur ma table de 100000 lignes pour exclure le hasard
la source
Très vieille question mais avec:
Vous avez un tableau d'enregistrement, trié par ordre aléatoire. Pas besoin de gemmes ou de scripts.
Si vous voulez un enregistrement:
la source
shuffle.first
==.sample
Je suis tout nouveau sur RoR mais j'ai obtenu que cela fonctionne pour moi:
C'est venu de:
Comment trier (brouiller) au hasard un tableau dans Ruby?
la source
array.shuffle
. Quoi qu'il en soit, méfiez-vous, carCard.all
cela chargera tous les enregistrements de la carte en mémoire, ce qui devient plus inefficace avec le nombre d'objets dont nous parlons.Que faire:
Pour moi c'est bien clair
la source
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:
la source
.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 silimit
est 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ù les0.5
moyens0.5%
. Cependant, je trouve que cette solution est encore lente si vous avez desWHERE
conditions qui filtrent beaucoup de lignes. Je suppose que c'est parce queTABLESAMPLE SYSTEM(0.5)
récupérez toutes les lignes avant que lesWHERE
conditions ne s'appliquent.Une autre solution pour les grandes tables (mais pas très aléatoires) est:
où
sample_size
peut être100
(mais pas trop grand sinon c'est lent et consomme beaucoup de mémoire), etlimit
peut être1
. Notez que bien que ce soit rapide mais pas vraiment aléatoire, il est aléatoiresample_size
uniquement 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.
la source
En plus de l'utilisation
RANDOM()
, vous pouvez également jeter ceci dans une portée:Ou, si vous n'aimez pas cela comme une portée, jetez-la simplement dans une méthode de classe. Fonctionne maintenant
Thing.random
avecThing.random(n)
.la source
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:
Par exemple, pour les tests, des échantillons de données auraient pu être créés au hasard de toute façon, c'est donc
take
plus que suffisant, et pour être honnête, mêmefirst
.https://guides.rubyonrails.org/active_record_querying.html#take
la source