Enregistrement aléatoire dans ActiveRecord
Je suis dans le besoin d'obtenir un enregistrement aléatoire à partir d'une table via ActiveRecord. J'ai suivi l'exemple de Jamis Buck à partir de 2006.
Cependant, j'ai aussi trouver un autre moyen via une recherche Google (ne peut pas d'un attribut avec un lien grâce à de nouvelles restrictions de l'utilisateur):
rand_id = rand(Model.count)
rand_record = Model.first(:conditions => ["id >= ?", rand_id])
Je suis curieux de voir comment d'autres ici l'ont fait ou si quelqu'un sait de quelle manière, ce serait plus efficace.
- 2 points qui pourraient aider à une réponse. 1. Comment uniformément distribués sont vos identifiants, sont-ils séquentielle? 2. Comment aléatoire t-il besoin d'être? Bon assez aléatoires, réel ou aléatoire?
- Ils sont séquentiels id généré automatiquement par activerecord et il a juste à être assez bon.
- Ensuite, votre solution proposée est proche de l'idéal 🙂 j'aimerais utiliser "SELECT MAX(id) from table_name" au lieu de COUNT(*) comme il traitera de lignes supprimées un peu mieux, sinon, le reste est très bien. En bref, si "suffisamment bonne" est ok, alors vous avez 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, simple rand fonctionne très bien.
- Cela ne fonctionne pas lorsque vous avez supprimé des lignes.
Vous devez vous connecter pour publier un commentaire.
Je n'ai pas trouvé un moyen idéal de le faire sans avoir au moins deux requêtes.
Celui-ci utilise un numéro généré de façon aléatoire (jusqu'à l'enregistrement en cours de comptage) comme un décalage.
Pour être honnête, je viens d'utiliser ORDER BY RAND() ou ALÉATOIRE() (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.
Model.find(:offset => offset).first
va jeter erreur. Je pense queModel.first(:offset => offset)
pourrait faire mieux.Thing.order("RANDOM()").limit(100)
pour 100 inscriptions choisies au hasard. (Sachez que c'estRANDOM()
dans PostgreSQL etRAND()
dans MySQL ... pas aussi portable que vous le désirez.)Model.count == 1
.Model.offset(offset).first
.Dans Rails 4 et 5, à l'aide de Postgresql ou SQLite, à l'aide de
RANDOM()
:Sans doute le même travail pour MySQL avec
RAND()
Ce est environ 2,5 fois plus rapide que l'approche de la accepté de répondre à.
Mise en garde: C'est lent pour les grands ensembles de données avec des millions d'enregistrements, de sorte que vous voudrez peut-être ajouter un
limit
clause.Votre exemple de code va commencer à se comporter de façon inexacte une fois que les dossiers sont supprimés (il va favoriser injustement les articles avec le plus faible ids)
Vous êtes probablement mieux d'utiliser l'aléatoire des méthodes à l'intérieur de votre base de données. Celles-ci varient selon la DB que vous utilisez, mais :commande => "RAND()" fonctionne pour mysql et :commande => "RANDOM()" fonctionne pour postgres
Model.order("RANDOM()").first
à la place..order("RAND()")
exige "l'Aide temporaire; à l'Aide de filesort".Analyse comparative de ces deux méthodes sur MySQL 5.1.49, Ruby 1.9.2p180 sur les produits de la table avec +5 milliers d'enregistrements:
Décalage dans MySQL semble être beaucoup plus lent.
MODIFIER
J'ai aussi essayé
Mais j'ai dû le tuer après environ 60 secondes. MySQL a été "la Copie de tmp table sur le disque". Qui ne va pas au travail.
Thing.order("RANDOM()").first
sur une table avec 250k entrées - la requête a terminé en vertu d'une demi-seconde. (PostgreSQL 9.0, REE 1.8.7, 2 x 2,66 GHz cœurs) Qui est assez rapide pour moi, depuis que je suis en train de faire un "nettoyage".rand_id = rand(Product.count) + 1
ou vous n'aurez jamais le dernier enregistrement.random1
ne fonctionnera pas si vous jamais supprimer une ligne dans la table. (Le comte sera de moins que le max id et vous ne serez jamais en mesure de sélectionner les lignes à haute id).random2
peut être améliorée par une#order
à l'aide d'une colonne indexée.Il n'a pas à être difficile.
pluck
retourne un tableau de tous les id de la table. Lesample
méthode sur le tableau, renvoie un id aléatoire à partir de la matrice.Cela devrait bien fonctionner avec la même probabilité de sélection et de soutien pour les tables avec des lignes supprimées. Vous pouvez même mélanger avec contraintes.
Et ainsi choisir aléatoirement utilisateur qui aime les vendredis plutôt que n'importe quel utilisateur.
J'ai fait un rails 3 gem pour le gérer:
https://github.com/spilliton/randumb
Vous permettent de faire des trucs comme ça:
ORDER BY RANDOM()
(ouRAND()
pour mysql) à votre requête." – donc, les commentaires sur les mauvaises performances mentionné dans les commentaires pour la réponse par @semanticart s'appliquent également lors de l'utilisation de ce joyau. Mais au moins, c'est DB indépendant.Il n'est pas conseillé que vous utilisez cette solution, mais si pour une raison quelconque vous vraiment voulez sélectionner au hasard un record alors que seulement faire une requête de base de données, vous pouvez utiliser le
sample
méthode de la Ruby classe Array, ce qui vous permet de sélectionner au hasard un élément d'un tableau.Cette méthode ne nécessite qu'requête de base de données, mais c'est nettement plus lent que les autres alternatives comme
Model.offset(rand(Model.count)).first
qui exigent deux requêtes de base de données, si ce dernier est toujours préféré.Model.last(100).sample
ou quelque chose avec l'idée de minceur dossiers.- Je utiliser ce si souvent à partir de la console j'étends ActiveRecord dans un initialiseur - Rails 4 exemple:
Je peux alors appel
Foo.random
de ramener un enregistrement aléatoire.limit(1)
?ActiveRecord#first
devrait être assez intelligents pour le faire.Une requête dans Postgres:
L'aide d'un décalage, deux requêtes:
Lecture de l'ensemble de ces ne m'a pas donné beaucoup de confiance sur lequel de ces serait le mieux dans mon cas en particulier avec des Rails 5 et MySQL/Maria 5.5. Donc, j'ai testé quelques réponses sur ~ 65000 dossiers, et de disposer des deux la vente à emporter:
limit
est un gagnant clair.pluck
+sample
.Cette réponse synthétise, valide et met à jour Mohamed répondre, ainsi que Nami WANG commentaire sur le même et Florian Pilz commentaire de la accepté de répondre - veuillez envoyer un maximum de voix pour eux!
Vous pouvez utiliser le
Array
méthodesample
, la méthodesample
retourne un objet aléatoire à partir d'un tableau, pour l'utiliser il vous suffit de exec dans un simpleActiveRecord
requête qui retourne une collection, par exemple:sera de retour à quelque chose comme ceci:
order('rand()').limit(1)
signifie "le même" travail (avec ~10K enregistrements).Si vous avez besoin de sélectionner certains des résultats aléatoires dans la portée:
Recommandons vivement ce petit bijou pour les enregistrements aléatoires, qui est spécialement conçu pour la table avec beaucoup de lignes de données:
https://github.com/haopingfan/quick_random_records
Toutes les autres réponses de mal fonctionner avec de grandes bases de données, à l'exception de ce bijou:
4.6ms
totalement.User.order('RAND()').limit(10)
coût733.0ms
.offset
approche coût245.4ms
totalement.User.all.sample(10)
approche coût573.4ms
.Note: Ma table n'a de 120 000 utilisateurs. Le plus de disques que vous avez, le plus énorme que la différence de performances.
La méthode Ruby pour les choisissant au hasard un élément d'une liste est
sample
. Voulant créer un efficacesample
pour ActiveRecord, et sur la base des réponses précédentes, j'ai utilisé:J'ai mis cela dans
lib/ext/sample.rb
et puis la charger avec cetteconfig/initializers/monkey_patches.rb
:Ce sera l'une requête si la taille du modèle est déjà dans le cache et deux sinon.
Rails 4.2 et Oracle:
Pour oracle, vous pouvez définir un champ d'application sur votre Modèle comme suit:
ou
Et puis pour un exemple de l'appeler comme ceci:
ou
bien sûr, vous pouvez également placer une commande sans portée comme ceci:
order('random()'
et MySQL avecorder('rand()')
ainsi. C'est certainement la meilleure réponse.Pour base de données MySQL essayer: Modèle.commande("RAND()").première
Si vous utilisez PostgreSQL 9.5+, vous pouvez profiter de
TABLESAMPLE
pour sélectionner un enregistrement aléatoire.Les deux par défaut les méthodes d'échantillonnage (
SYSTEM
etBERNOULLI
) exiger que vous spécifiez le nombre de lignes à retourner en pourcentage du nombre total de lignes dans la table.Cela nécessite de connaître le nombre d'enregistrements dans la table pour sélectionner le pourcentage approprié, qui peut ne pas être facile à trouver rapidement. Heureusement, il y a le
tsm_system_rows
module qui vous permet de spécifier le nombre de lignes à retourner directement.Pour utiliser cette dans ActiveRecord, d'abord activer l'extension à l'intérieur d'une migration:
Ensuite modifier le
from
de la clause de la requête:Je ne sais pas si le
SYSTEM_ROWS
de la méthode d'échantillonnage sera entièrement aléatoire, ou si elle retourne la première ligne d'une page au hasard.La plupart de ces informations ont été prises à partir d'un 2ndQuadrant billet de blog écrit par Gulcin Yildirim.
Après avoir vu tant de réponses, j'ai décidé de comparer tous sur mon PostgreSQL(9.6.3) de la base de données. J'utilise une petite table de 100 000 et de s'en débarrasser le Modèle.commande("RANDOM()").en premier, car il était déjà deux ordres de grandeur plus lent.
L'aide d'une table avec de 2 500 000 entrées avec 10 colonnes les mains vers le bas le gagnant a été l'arracher d'une méthode de près de 8 fois plus rapide que le runner up(offset. J'ai seulement couru ce sur un serveur local, de sorte que le nombre peut être gonflé mais sa plus grande que l'arracher méthode est ce que je vais utiliser. Il est également intéressant de noter que cela pourrait poser des problèmes, c'est vous arracher plus de 1 résultat à la fois, parce que chacune de celles-ci sera unique aka moins aléatoire.
Arracher gagne 100 fois sur mon de 25 000 000 de lignes de la table
Edit: en fait ce temps comprend le cueillir dans la boucle si je la sors elle il fonctionne à peu près aussi rapide que simple itération sur l'id. Cependant, tout cela prend une bonne quantité de mémoire vive.
Voici les données de l'exécution de 2000 fois sur mon de 100 000 de lignes de table de la règle de manière aléatoire et
Je suis tout nouveau à RoR, mais j'ai eu que cela fonctionne pour moi:
Il est venu à partir de:
Comment au hasard de tri (scramble) un tableau en Ruby?
array.shuffle
. De toute façon, méfiez-vous, commeCard.all
charge tous les dossiers de carte en mémoire, ce qui devient de plus en plus inefficace, plus les objets dont nous parlons.Ce faire:
Pour moi, c'est bien clair
J'essaie ce de Sam exemple sur mon Application à l'aide de rails 4.2.8 de Référence( j'ai mis 1..Catégorie.le comte de hasard, parce que si le hasard prend un 0, il produira une erreur(ActiveRecord::RecordNotFound: impossible de trouver de la Catégorie avec 'id'=0)) et de la mine:
.order('RANDOM()').limit(limit)
l'air soigné, mais qui est lent pour les grandes tables, car il a besoin de récupérer et trier toutes les lignes, même silimit
est 1 (en interne dans la base de données, mais pas dans les Rails). Je ne suis pas sûr à propos de MySQL, mais ce qui se passe dans Postgres. Plus d'explications dans ici et ici.Une solution pour les grandes tables est
.from("products TABLESAMPLE SYSTEM(0.5)")
où0.5
signifie0.5%
. Cependant, je trouve cette solution est encore lent si vous avezWHERE
conditions qui filtrent beaucoup de lignes. Je suppose que c'est parce queTABLESAMPLE SYSTEM(0.5)
extraire toutes les lignes avant deWHERE
conditions s'appliquent.Une autre solution pour les grandes tables (mais pas très aléatoire) 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 même si c'est rapide, mais il n'est pas vraiment aléatoire, c'est de l'aléatoire danssample_size
dossiers seulement.PS: résultats des tests dans les réponses ci-dessus ne sont pas fiables (au moins dans Postgres) parce que certaines requêtes DB course à la 2ème temps peut être considérablement plus rapide que la course à la 1ère fois, grâce à DB cache. Et malheureusement, il n'existe pas de moyen facile de désactiver le cache dans Postgres pour faire de ces points de référence fiables.
Très vieille question, mais avec :
Vous avez un Tableau d'enregistrement, de les classer par ordre aléatoire.
Pas besoin de pierres précieuses ou de scripts.
Si vous souhaitez un enregistrement :
shuffle.first
==.sample
Avec l'aide de
RANDOM()
, vous pouvez également jeter dans un champ:Ou, si vous n'avez pas envie que comme un champ, il suffit de jeter dans une méthode de classe. Maintenant
Thing.random
travaille avecThing.random(n)
.