Enregistrement aléatoire de MongoDB

336

Je cherche à obtenir un enregistrement aléatoire d'un énorme (100 millions d'enregistrements) mongodb.

Quel est le moyen le plus rapide et le plus efficace de le faire? Les données sont déjà là et il n'y a aucun champ dans lequel je peux générer un nombre aléatoire et obtenir une ligne aléatoire.

Aucune suggestion?

Will M
la source
2
Voir aussi cette question SO intitulée "Ordonner un jeu de résultats au hasard dans mongo" . Penser à ordonner au hasard un ensemble de résultats est une version plus générale de cette question - plus puissante et plus utile.
David J.15
11
Cette question revient sans cesse. Les dernières informations peuvent être trouvées à la demande de fonctionnalité pour obtenir des éléments aléatoires d'une collection dans le tracker de ticket MongoDB. S'il était implémenté en mode natif, ce serait probablement l'option la plus efficace. (Si vous voulez la fonctionnalité, allez voter.)
David J.
S'agit-il d'une collection éclatée?
Dylan Tong
3
La réponse correcte a été donnée par @JohnnyHK ci-dessous: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian
Est-ce que quelqu'un sait combien c'est plus lent que de simplement prendre le premier disque? Je me demande s'il vaut la peine de prélever un échantillon aléatoire pour faire quelque chose ou simplement le faire dans l'ordre.
David Kong

Réponses:

248

À partir de la version 3.2 de MongoDB, vous pouvez obtenir N documents aléatoires à partir d'une collection à l'aide de l' $sampleopérateur de pipeline d'agrégation:

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

Si vous souhaitez sélectionner le ou les documents aléatoires dans un sous-ensemble filtré de la collection, ajoutez une $matchétape au pipeline:

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

Comme indiqué dans les commentaires, lorsque sizeest supérieur à 1, il peut y avoir des doublons dans l'échantillon de document renvoyé.

JohnnyHK
la source
12
C'est un bon moyen, mais n'oubliez pas que cela NE garantit PAS qu'il n'y a pas de copies du même objet dans l'échantillon.
Matheus Araujo
10
@MatheusAraujo qui n'aura pas d'importance si vous voulez un record mais bon point quand même
Toby
3
Ne pas être pédant, mais la question ne spécifie pas de version MongoDB, donc je suppose que la version la plus récente est raisonnable.
dalanmiller
2
@Nepoxx Voir les documents concernant le traitement impliqué.
JohnnyHK
2
@brycejl Cela aurait la faille fatale de ne rien faire correspondre si l'étape $ sample ne sélectionnait aucun document correspondant.
JohnnyHK
115

Effectuez un décompte de tous les enregistrements, générez un nombre aléatoire entre 0 et le décompte, puis procédez comme suit:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
ceejayoz
la source
139
Malheureusement, skip () est plutôt inefficace car il doit numériser autant de documents. En outre, il existe une condition de concurrence si des lignes sont supprimées entre l'obtention du nombre et l'exécution de la requête.
mstearn
6
Notez que le nombre aléatoire doit être compris entre 0 et le nombre (exclusif). Autrement dit, si vous avez 10 éléments, le nombre aléatoire doit être compris entre 0 et 9. Sinon, le curseur pourrait essayer de passer le dernier élément et rien ne serait retourné.
mat
4
Merci, a parfaitement fonctionné pour mes besoins. @mstearn, vos commentaires sur l'efficacité et les conditions de concurrence sont valides, mais pour les collections où rien ne compte (extrait de lot côté serveur dans une collection où les enregistrements ne sont pas supprimés), cela est largement supérieur au hacky (IMO) solution dans le Mongo Cookbook.
Michael Moussa
4
que fait la fixation de la limite à -1?
MonkeyBonkey
@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Si numberToReturn vaut 0, la base de données utilisera la taille de retour par défaut. Si le nombre est négatif, la base de données renverra ce numéro et fermera le curseur. "
ceejayoz
86

Mise à jour pour MongoDB 3.2

3.2 a introduit $ sample dans le pipeline d'agrégation.

Il y a aussi un bon article de blog sur la mise en pratique.

Pour les anciennes versions (réponse précédente)

Il s'agissait en fait d'une demande de fonctionnalité: http://jira.mongodb.org/browse/SERVER-533 mais elle a été déposée sous "Ne sera pas corrigé".

Le livre de cuisine a une très bonne recette pour sélectionner un document au hasard dans une collection: http://cookbook.mongodb.org/patterns/random-attribute/

Pour paraphraser la recette, vous attribuez des numéros aléatoires à vos documents:

db.docs.save( { key : 1, ..., random : Math.random() } )

Sélectionnez ensuite un document aléatoire:

rand = Math.random()
result = db.docs.findOne( { key : 2, random : { $gte : rand } } )
if ( result == null ) {
  result = db.docs.findOne( { key : 2, random : { $lte : rand } } )
}

Interroger avec les deux $gteet $lteest nécessaire pour trouver le document avec un nombre aléatoire le plus procherand .

Et bien sûr, vous voudrez indexer sur le champ aléatoire:

db.docs.ensureIndex( { key : 1, random :1 } )

Si vous interrogez déjà un index, déposez-le simplement, ajoutez random: 1-le et ajoutez-le à nouveau.

Michael
la source
7
Et voici un moyen simple d'ajouter le champ aléatoire à chaque document de la collection. function setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey
8
Cela sélectionne un document au hasard, mais si vous le faites plusieurs fois, les recherches ne sont pas indépendantes. Vous êtes plus susceptible d'obtenir le même document deux fois de suite que le hasard ne le dicterait.
manque le
12
On dirait une mauvaise implémentation du hachage circulaire. C'est encore pire que ce que manque à dire: même une seule recherche est biaisée parce que les nombres aléatoires ne sont pas uniformément répartis. Pour le faire correctement, vous auriez besoin d'un ensemble de, disons, 10 nombres aléatoires par document. Plus vous utilisez de nombres aléatoires par document, plus la distribution de sortie devient uniforme.
Thomas
4
Le ticket MongoDB JIRA est toujours vivant: jira.mongodb.org/browse/SERVER-533 Allez commenter et voter si vous voulez la fonctionnalité.
David
1
Prenez note du type de mise en garde mentionné. Cela ne fonctionne pas efficacement avec une petite quantité de documents. Étant donné deux éléments avec une clé aléatoire de 3 et 63. Le document # 63 sera choisi plus fréquemment, où $gteest le premier. La solution alternative stackoverflow.com/a/9499484/79201 fonctionnerait mieux dans ce cas.
Ryan Schumacher
56

Vous pouvez également utiliser la fonction d'indexation géospatiale de MongoDB pour sélectionner les documents «les plus proches» d'un nombre aléatoire.

Tout d'abord, activez l'indexation géospatiale sur une collection:

db.docs.ensureIndex( { random_point: '2d' } )

Pour créer un groupe de documents avec des points aléatoires sur l'axe X:

for ( i = 0; i < 10; ++i ) {
    db.docs.insert( { key: i, random_point: [Math.random(), 0] } );
}

Ensuite, vous pouvez obtenir un document aléatoire de la collection comme ceci:

db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )

Ou vous pouvez récupérer plusieurs documents les plus proches d'un point aléatoire:

db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )

Cela ne nécessite qu'une seule requête et aucune vérification nulle, de plus le code est propre, simple et flexible. Vous pouvez même utiliser l'axe Y du géopoint pour ajouter une deuxième dimension de caractère aléatoire à votre requête.

Nico de Poel
la source
8
J'aime cette réponse, c'est la plus efficace que j'ai vue qui ne nécessite pas beaucoup de plaisanteries côté serveur.
Tony Million
4
Cela est également biaisé vers les documents qui se trouvent avoir peu de points dans leur voisinage.
Thomas
6
C'est vrai, et il y a aussi d'autres problèmes: les documents sont fortement corrélés sur leurs clés aléatoires, il est donc très prévisible quels documents seront renvoyés en tant que groupe si vous sélectionnez plusieurs documents. De plus, les documents proches des limites (0 et 1) sont moins susceptibles d'être choisis. Ce dernier pourrait être résolu en utilisant la géocartographie sphérique, qui s'enroule autour des bords. Cependant, vous devriez voir cette réponse comme une version améliorée de la recette du livre de cuisine, et non comme un mécanisme de sélection aléatoire parfait. C'est assez aléatoire pour la plupart des usages.
Nico de Poel
@NicodePoel, j'aime votre réponse ainsi que votre commentaire! Et j'ai quelques questions à vous poser: 1- Comment savez-vous que les points proches des bornes 0 et 1 sont moins susceptibles d'être choisis, est-ce basé sur un terrain mathématique?, 2- Pouvez-vous élaborer davantage sur la géocartographie sphérique, comment cela améliorera la sélection aléatoire, et comment le faire dans MongoDB? ... apprécié!
securecurve
Enrichissez votre idée. Enfin, j'ai un excellent code qui est très convivial pour le CPU et la RAM! Merci
Qais Bsharat
21

La recette suivante est un peu plus lente que la solution de livre de cuisine mongo (ajoutez une clé aléatoire sur chaque document), mais retourne des documents aléatoires plus uniformément répartis. Elle est un peu moins uniformément distribuée que la skip( random )solution, mais beaucoup plus rapide et plus sûre en cas de suppression de documents.

function draw(collection, query) {
    // query: mongodb query object (optional)
    var query = query || { };
    query['random'] = { $lte: Math.random() };
    var cur = collection.find(query).sort({ rand: -1 });
    if (! cur.hasNext()) {
        delete query.random;
        cur = collection.find(query).sort({ rand: -1 });
    }
    var doc = cur.next();
    doc.random = Math.random();
    collection.update({ _id: doc._id }, doc);
    return doc;
}

Cela vous oblige également à ajouter un champ aléatoire "aléatoire" à vos documents, alors n'oubliez pas de l'ajouter lorsque vous les créez: vous devrez peut-être initialiser votre collection comme indiqué par Geoffrey

function addRandom(collection) { 
    collection.find().forEach(function (obj) {
        obj.random = Math.random();
        collection.save(obj);
    }); 
} 
db.eval(addRandom, db.things);

Résultats de référence

Cette méthode est beaucoup plus rapide que la skip() méthode (de ceejayoz) et génère des documents plus uniformément aléatoires que la méthode du "livre de cuisine" rapportée par Michael:

Pour une collection avec 1 000 000 d'éléments:

  • Cette méthode prend moins d'une milliseconde sur ma machine

  • la skip()méthode prend 180 ms en moyenne

La méthode du livre de cuisine empêchera de sélectionner un grand nombre de documents, car leur nombre aléatoire ne leur est pas favorable.

  • Cette méthode sélectionnera tous les éléments de manière uniforme au fil du temps.

  • Dans mon indice de référence, il n'était que 30% plus lent que la méthode du livre de cuisine.

  • le caractère aléatoire n'est pas parfait à 100% mais il est très bon (et il peut être amélioré si nécessaire)

Cette recette n'est pas parfaite - la solution parfaite serait une fonction intégrée comme d'autres l'ont noté.
Cependant, ce devrait être un bon compromis à de nombreuses fins.

spam_eggs
la source
10

Voici un moyen d'utiliser les ObjectIdvaleurs par défaut _idet un peu de mathématiques et de logique.

// Get the "min" and "max" timestamp values from the _id in the collection and the 
// diff between.
// 4-bytes from a hex string is 8 characters

var min = parseInt(db.collection.find()
        .sort({ "_id": 1 }).limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    max = parseInt(db.collection.find()
        .sort({ "_id": -1 })limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
    diff = max - min;

// Get a random value from diff and divide/multiply be 1000 for The "_id" precision:
var random = Math.floor(Math.floor(Math.random(diff)*diff)/1000)*1000;

// Use "random" in the range and pad the hex string to a valid ObjectId
var _id = new ObjectId(((min + random)/1000).toString(16) + "0000000000000000")

// Then query for the single document:
var randomDoc = db.collection.find({ "_id": { "$gte": _id } })
   .sort({ "_id": 1 }).limit(1).toArray()[0];

C'est la logique générale de la représentation shell et facilement adaptable.

Donc en points:

  • Rechercher les valeurs de clé primaire min et max dans la collection

  • Générez un nombre aléatoire qui se situe entre les horodatages de ces documents.

  • Ajoutez le nombre aléatoire à la valeur minimale et recherchez le premier document supérieur ou égal à cette valeur.

Cela utilise "padding" à partir de la valeur d'horodatage dans "hex" pour former une ObjectIdvaleur valide puisque c'est ce que nous recherchons. L'utilisation d'entiers comme _idvaleur est essentiellement plus simple mais la même idée de base dans les points.

Blakes Seven
la source
J'ai une collection de 300 000 000 lignes. C'est la seule solution qui fonctionne et elle est assez rapide.
Nikos
8

En Python avec pymongo:

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]
Jabba
la source
5
Il convient de noter qu'en interne, cela utilisera le saut et la limite, tout comme la plupart des autres réponses.
JohnnyHK
Votre réponse est correcte. Cependant, veuillez le remplacer count()par estimated_document_count()as count()est déconseillé dans Mongdo v4.2.
user3848207
8

Vous pouvez maintenant utiliser l'agrégat. Exemple:

db.users.aggregate(
   [ { $sample: { size: 3 } } ]
)

Voir le doc .

dbam
la source
3
Remarque: $ sample peut obtenir le même document plusieurs fois
Saman Shafigh
6

c'est difficile s'il n'y a pas de données à déconnecter. quels sont le champ _id? sont-ils des identifiants d'objet mongodb? Si oui, vous pouvez obtenir les valeurs les plus élevées et les plus basses:

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

alors si vous supposez que les identifiants sont distribués uniformément (mais ils ne le sont pas, mais au moins c'est un début):

unsigned long long L = first_8_bytes_of(lowest)
unsigned long long H = first_8_bytes_of(highest)

V = (H - L) * random_from_0_to_1();
N = L + V;
oid = N concat random_4_bytes();

randomobj = db.coll.find({_id:{$gte:oid}}).limit(1);
dm.
la source
1
Des idées à quoi cela ressemblerait-il en PHP? ou au moins quelle langue avez-vous utilisé ci-dessus? est-ce Python?
Marcin
6

En utilisant Python (pymongo), la fonction d'agrégation fonctionne également.

collection.aggregate([{'$sample': {'size': sample_size }}])

Cette approche est beaucoup plus rapide que d'exécuter une requête pour un nombre aléatoire (par exemple collection.find ([random_int]). C'est particulièrement le cas pour les grandes collections.

Daniel
la source
5

Vous pouvez choisir un horodatage aléatoire et rechercher le premier objet créé par la suite. Il ne numérisera qu'un seul document, bien qu'il ne vous donne pas nécessairement une distribution uniforme.

var randRec = function() {
    // replace with your collection
    var coll = db.collection
    // get unixtime of first and last record
    var min = coll.find().sort({_id: 1}).limit(1)[0]._id.getTimestamp() - 0;
    var max = coll.find().sort({_id: -1}).limit(1)[0]._id.getTimestamp() - 0;

    // allow to pass additional query params
    return function(query) {
        if (typeof query === 'undefined') query = {}
        var randTime = Math.round(Math.random() * (max - min)) + min;
        var hexSeconds = Math.floor(randTime / 1000).toString(16);
        var id = ObjectId(hexSeconds + "0000000000000000");
        query._id = {$gte: id}
        return coll.find(query).limit(1)
    };
}();
Martin Nowak
la source
Il serait facilement possible de biaiser la date aléatoire pour tenir compte de la croissance de la base de données superlinéaire.
Martin Nowak
c'est la meilleure méthode pour les très grandes collections, elle fonctionne à O (1), unline skip () ou count () utilisé dans les autres solutions ici
marmor
4

Ma solution sur php:

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}
code_turist
la source
3

Afin d'obtenir un nombre déterminé de documents aléatoires sans doublons:

  1. obtenir d'abord tous les identifiants
  2. obtenir la taille des documents
  3. boucle obtenant un index aléatoire et sautant dupliqué

    number_of_docs=7
    db.collection('preguntas').find({},{_id:1}).toArray(function(err, arr) {
    count=arr.length
    idsram=[]
    rans=[]
    while(number_of_docs!=0){
        var R = Math.floor(Math.random() * count);
        if (rans.indexOf(R) > -1) {
         continue
          } else {           
                   ans.push(R)
                   idsram.push(arr[R]._id)
                   number_of_docs--
                    }
        }
    db.collection('preguntas').find({}).toArray(function(err1, doc1) {
                    if (err1) { console.log(err1); return;  }
                   res.send(doc1)
                });
            });
Fabio Guerra
la source
2

Je suggérerais d'utiliser map / Reduce, où vous utilisez la fonction map pour n'émettre que lorsqu'une valeur aléatoire est supérieure à une probabilité donnée.

function mapf() {
    if(Math.random() <= probability) {
    emit(1, this);
    }
}

function reducef(key,values) {
    return {"documents": values};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": { "probability": 0.5}});
printjson(res.results);

La fonction de réduction ci-dessus fonctionne car une seule touche ('1') est émise par la fonction de carte.

La valeur de la "probabilité" est définie dans la "portée", lors de l'appel de mapRreduce (...)

L'utilisation de mapReduce comme ceci devrait également être utilisable sur une base de données fragmentée.

Si vous souhaitez sélectionner exactement n de m documents dans la base de données, vous pouvez le faire comme ceci:

function mapf() {
    if(countSubset == 0) return;
    var prob = countSubset / countTotal;
    if(Math.random() <= prob) {
        emit(1, {"documents": [this]}); 
        countSubset--;
    }
    countTotal--;
}

function reducef(key,values) {
    var newArray = new Array();
for(var i=0; i < values.length; i++) {
    newArray = newArray.concat(values[i].documents);
}

return {"documents": newArray};
}

res = db.questions.mapReduce(mapf, reducef, {"out": {"inline": 1}, "scope": {"countTotal": 4, "countSubset": 2}})
printjson(res.results);

Où "countTotal" (m) est le nombre de documents dans la base de données et "countSubset" (n) est le nombre de documents à récupérer.

Cette approche pourrait poser certains problèmes sur les bases de données fragmentées.

torbenl
la source
4
Faire une analyse complète de la collection pour retourner 1 élément ... ce doit être la technique la moins efficace pour le faire.
Thomas
1
L'astuce est que c'est une solution générale pour renvoyer un nombre arbitraire d'éléments aléatoires - auquel cas ce serait plus rapide que les autres solutions pour obtenir> 2 éléments aléatoires.
torbenl
2

Vous pouvez choisir random _id et renvoyer l'objet correspondant:

 db.collection.count( function(err, count){
        db.collection.distinct( "_id" , function( err, result) {
            if (err)
                res.send(err)
            var randomId = result[Math.floor(Math.random() * (count-1))]
            db.collection.findOne( { _id: randomId } , function( err, result) {
                if (err)
                    res.send(err)
                console.log(result)
            })
        })
    })

Ici, vous n'avez pas besoin de dépenser de l'espace pour stocker des nombres aléatoires dans la collection.

Vijay13
la source
1

Je suggère d'ajouter un champ int aléatoire à chaque objet. Ensuite, vous pouvez simplement faire un

findOne({random_field: {$gte: rand()}}) 

pour choisir un document aléatoire. Assurez-vous simplement que vous assurezIndex ({random_field: 1})

mstearn
la source
2
Si le premier enregistrement de votre collection a une valeur random_field relativement élevée, ne sera-t-il pas renvoyé presque tout le temps?
thehiatus
2
thehaitus est correct, il le fera - il ne convient à aucun but
Heptic
7
Cette solution est complètement fausse, l'ajout d'un nombre aléatoire (imaginons entre 0 et 2 ^ 32-1) ne garantit aucune bonne distribution et l'utilisation de $ gte le rend encore pire, car votre sélection aléatoire ne sera même pas proche à un nombre pseudo-aléatoire. Je suggère de ne jamais utiliser ce concept.
Maximiliano Rios
1

Lorsque j'ai été confronté à une solution similaire, j'ai fait marche arrière et j'ai constaté que la demande commerciale visait en fait à créer une certaine forme de rotation de l'inventaire présenté. Dans ce cas, il existe de bien meilleures options, qui ont des réponses de moteurs de recherche comme Solr, pas de magasins de données comme MongoDB.

En bref, avec l'exigence de "faire pivoter intelligemment" le contenu, ce que nous devrions faire au lieu d'un nombre aléatoire dans tous les documents est d'inclure un modificateur de score q personnel. Pour l'implémenter vous-même, en supposant une petite population d'utilisateurs, vous pouvez stocker un document par utilisateur qui a le productId, le nombre d'impressions, le nombre de clics, la dernière date vue et tous les autres facteurs que l'entreprise trouve utiles pour calculer le score aq modificateur. Lors de la récupération de l'ensemble à afficher, vous demandez généralement plus de documents de la banque de données que ne le demande l'utilisateur final, puis appliquez le modificateur de score q, prenez le nombre d'enregistrements demandés par l'utilisateur final, puis randomisez la page de résultats, un tout petit défini, il vous suffit donc de trier les documents dans la couche application (en mémoire).

Si l'univers des utilisateurs est trop grand, vous pouvez classer les utilisateurs en groupes de comportements et indexer par groupe de comportements plutôt que par utilisateur.

Si l'univers des produits est suffisamment petit, vous pouvez créer un index par utilisateur.

J'ai trouvé que cette technique était beaucoup plus efficace, mais surtout plus efficace pour créer une expérience pertinente et utile de l'utilisation de la solution logicielle.

paegun
la source
1

aucune des solutions n'a bien fonctionné pour moi. surtout quand il y a beaucoup de lacunes et que le jeu est petit. cela a très bien fonctionné pour moi (en php):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();
Mantas Karanauskas
la source
Vous spécifiez la langue, mais pas la bibliothèque que vous utilisez?
Benjamin
Pour info, il y a une condition de concurrence ici si un document est supprimé entre la première et la troisième ligne. De plus find+ skipest assez mauvais, vous retournez tous les documents juste pour choisir un: S.
Martin Konecny
1

Mon tri / ordre PHP / MongoDB par solution RANDOM. J'espère que cela aide n'importe qui.

Remarque: J'ai des ID numériques dans ma collection MongoDB qui font référence à un enregistrement de base de données MySQL.

Je crée d'abord un tableau avec 10 nombres générés aléatoirement

    $randomNumbers = [];
    for($i = 0; $i < 10; $i++){
        $randomNumbers[] = rand(0,1000);
    }

Dans mon agrégation, j'utilise l'opérateur de pipeline $ addField combiné avec $ arrayElemAt et $ mod (module). L'opérateur de module me donnera un nombre de 0 à 9 que j'utilise ensuite pour choisir un nombre dans le tableau avec des nombres générés de manière aléatoire.

    $aggregate[] = [
        '$addFields' => [
            'random_sort' => [ '$arrayElemAt' => [ $randomNumbers, [ '$mod' => [ '$my_numeric_mysql_id', 10 ] ] ] ],
        ],
    ];

Après cela, vous pouvez utiliser le pipeline de tri.

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];
feskr
la source
0

Si vous avez une clé d'identification simple, vous pouvez stocker tous les identifiants dans un tableau, puis choisir un identifiant aléatoire. (Réponse rubis):

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first
M. Demetrius Michael
la source
0

En utilisant Map / Reduce, vous pouvez certainement obtenir un enregistrement aléatoire, mais pas nécessairement très efficacement en fonction de la taille de la collection filtrée résultante avec laquelle vous finissez par travailler.

J'ai testé cette méthode avec 50000 documents (le filtre la réduit à environ 30000), et elle s'exécute en environ 400 ms sur un Intel i3 avec 16 Go de RAM et un disque dur SATA3 ...

db.toc_content.mapReduce(
    /* map function */
    function() { emit( 1, this._id ); },

    /* reduce function */
    function(k,v) {
        var r = Math.floor((Math.random()*v.length));
        return v[r];
    },

    /* options */
    {
        out: { inline: 1 },
        /* Filter the collection to "A"ctive documents */
        query: { status: "A" }
    }
);

La fonction Carte crée simplement un tableau des identifiants de tous les documents qui correspondent à la requête. Dans mon cas, j'ai testé cela avec environ 30 000 des 50 000 documents possibles.

La fonction Reduce choisit simplement un entier aléatoire entre 0 et le nombre d'éléments (-1) dans le tableau, puis renvoie cet _id dans le tableau.

400 ms sonne comme une longue période, et c'est vraiment le cas, si vous aviez cinquante millions d'enregistrements au lieu de cinquante mille, cela peut augmenter les frais généraux au point où ils deviennent inutilisables dans des situations multi-utilisateurs.

Il existe un problème ouvert pour MongoDB d'inclure cette fonctionnalité dans le noyau ... https://jira.mongodb.org/browse/SERVER-533

Si cette sélection "aléatoire" était intégrée dans une recherche d'index au lieu de collecter des identifiants dans un tableau, puis d'en sélectionner un, cela aiderait incroyablement. (allez voter!)

double hélice
la source
0

Cela fonctionne bien, c'est rapide, fonctionne avec plusieurs documents et ne nécessite pas de randchamp de remplissage, qui finira par se remplir:

  1. ajouter un index au champ .rand de votre collection
  2. utilisez la recherche et le rafraîchissement, quelque chose comme:
// Install packages:
//   npm install mongodb async
// Add index in mongo:
//   db.ensureIndex('mycollection', { rand: 1 })

var mongodb = require('mongodb')
var async = require('async')

// Find n random documents by using "rand" field.
function findAndRefreshRand (collection, n, fields, done) {
  var result = []
  var rand = Math.random()

  // Append documents to the result based on criteria and options, if options.limit is 0 skip the call.
  var appender = function (criteria, options, done) {
    return function (done) {
      if (options.limit > 0) {
        collection.find(criteria, fields, options).toArray(
          function (err, docs) {
            if (!err && Array.isArray(docs)) {
              Array.prototype.push.apply(result, docs)
            }
            done(err)
          }
        )
      } else {
        async.nextTick(done)
      }
    }
  }

  async.series([

    // Fetch docs with unitialized .rand.
    // NOTE: You can comment out this step if all docs have initialized .rand = Math.random()
    appender({ rand: { $exists: false } }, { limit: n - result.length }),

    // Fetch on one side of random number.
    appender({ rand: { $gte: rand } }, { sort: { rand: 1 }, limit: n - result.length }),

    // Continue fetch on the other side.
    appender({ rand: { $lt: rand } }, { sort: { rand: -1 }, limit: n - result.length }),

    // Refresh fetched docs, if any.
    function (done) {
      if (result.length > 0) {
        var batch = collection.initializeUnorderedBulkOp({ w: 0 })
        for (var i = 0; i < result.length; ++i) {
          batch.find({ _id: result[i]._id }).updateOne({ rand: Math.random() })
        }
        batch.execute(done)
      } else {
        async.nextTick(done)
      }
    }

  ], function (err) {
    done(err, result)
  })
}

// Example usage
mongodb.MongoClient.connect('mongodb://localhost:27017/core-development', function (err, db) {
  if (!err) {
    findAndRefreshRand(db.collection('profiles'), 1024, { _id: true, rand: true }, function (err, result) {
      if (!err) {
        console.log(result)
      } else {
        console.error(err)
      }
      db.close()
    })
  } else {
    console.error(err)
  }
})

ps. Comment trouver des enregistrements aléatoires dans la question mongodb est marqué comme doublon de cette question. La différence est que cette question demande explicitement de dossier unique que l'autre explicitement à obtenir le document au hasard s .

Mirek Rusin
la source
-2

Si vous utilisez mongoid, l'encapsuleur document-objet, vous pouvez effectuer les opérations suivantes dans Ruby. (En supposant que votre modèle est utilisateur)

User.all.to_a[rand(User.count)]

Dans mon .irbrc, j'ai

def rando klass
    klass.all.to_a[rand(klass.count)]
end

donc dans la console des rails, je peux faire, par exemple,

rando User
rando Article

pour obtenir des documents au hasard dans n'importe quelle collection.

Zack Xu
la source
1
Ceci est terriblement inefficace car il lira toute la collection dans un tableau puis choisira un enregistrement.
JohnnyHK
Ok, peut-être inefficace, mais sûrement pratique. essayez ceci si la taille de vos données n'est pas trop grande
Zack Xu
3
Bien sûr, mais la question initiale était pour une collection avec 100 millions de documents, donc ce serait une très mauvaise solution pour ce cas!
JohnnyHK
-2

vous pouvez également utiliser le shuffle-array après avoir exécuté votre requête

var shuffle = require ('shuffle-array');

Accounts.find (qry, function (err, results_array) {newIndexArr = shuffle (results_array);

rabie jegham
la source
-7

Ce qui fonctionne de manière efficace et fiable est le suivant:

Ajoutez un champ appelé "aléatoire" à chaque document et attribuez-lui une valeur aléatoire, ajoutez un index pour le champ aléatoire et procédez comme suit:

Supposons que nous ayons une collection de liens Web appelés "liens" et que nous en voulions un lien aléatoire:

link = db.links.find().sort({random: 1}).limit(1)[0]

Pour vous assurer que le même lien n'apparaîtra pas une deuxième fois, mettez à jour son champ aléatoire avec un nouveau nombre aléatoire:

db.links.update({random: Math.random()}, link)
accident ferroviaire
la source
2
pourquoi mettre à jour la base de données alors que vous pouvez simplement sélectionner une clé aléatoire différente?
Jason S
Il se peut que vous ne disposiez pas d'une liste des touches à sélectionner au hasard.
Mike
Vous devez donc trier toute la collection à chaque fois? Et qu'en est-il des enregistrements malchanceux qui ont obtenu de grands nombres aléatoires? Ils ne seront jamais sélectionnés.
Fantius
1
Vous devez le faire car les autres solutions, en particulier celle suggérée dans le livre MongoDB, ne fonctionnent pas. Si la première recherche échoue, la deuxième recherche renvoie toujours l'élément avec la plus petite valeur aléatoire. Si vous indexez de manière descendante de manière aléatoire, la première requête renvoie toujours l'élément avec le plus grand nombre aléatoire.
trainwreck
Ajout d'un champ dans chaque document? Je pense que ce n'est pas conseillé.
CS_noob