Record casuale da MongoDB


336

Sto cercando di ottenere un record casuale da un enorme (record di 100 milioni) mongodb .

Qual è il modo più rapido ed efficiente per farlo? I dati sono già lì e non ci sono campi in cui posso generare un numero casuale e ottenere una riga casuale.

Eventuali suggerimenti?


2
Vedi anche questa domanda SO intitolata "Ordinare un risultato impostato casualmente in mongo" . Pensare all'ordinamento casuale di un set di risultati è una versione più generale di questa domanda, più potente e più utile.
David J.

11
Questa domanda continua a spuntare. Le informazioni più recenti sono probabilmente disponibili alla richiesta della funzione per ottenere elementi casuali da una raccolta nel tracker ticket MongoDB. Se implementato in modo nativo, sarebbe probabilmente l'opzione più efficiente. (Se si desidera la funzionalità, andare a votare.)
David J.

Questa è una collezione frammentata?
Dylan Tong,

3
La risposta corretta è stata data da @JohnnyHK di seguito: db.mycoll.aggregate ({$ sample: {size: 1}})
Florian

Qualcuno sa quanto è più lento di quanto non sia semplicemente prendere il primo disco? Sto discutendo se vale la pena prelevare un campione casuale per fare qualcosa contro semplicemente farlo in ordine.
David Kong,

Risposte:


248

A partire dalla versione 3.2 di MongoDB, è possibile ottenere N documenti casuali da una raccolta utilizzando l' $sampleoperatore della pipeline di aggregazione:

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

Se si desidera selezionare i documenti casuali da un sottoinsieme filtrato della raccolta, anteporre una $matchfase alla pipeline:

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

Come notato nei commenti, quando sizeè maggiore di 1, potrebbero esserci duplicati nell'esempio di documento restituito.


12
Questo è un buon modo, ma ricorda che NON garantisce che non ci siano copie dello stesso oggetto nell'esempio.
Matheus Araujo,

10
@MatheusAraujo che non importa se vuoi un disco ma comunque un buon punto
Toby

3
Non essere pedante ma la domanda non specifica una versione di MongoDB, quindi suppongo che avere la versione più recente sia ragionevole.
Dalanmiller,

2
@Nepoxx Consulta i documenti relativi all'elaborazione in questione.
JohnnyHK,

2
@brycejl Ciò avrebbe il fatale difetto di non abbinare nulla se la fase $ sample non avesse selezionato alcun documento corrispondente.
JohnnyHK,

115

Esegui un conteggio di tutti i record, genera un numero casuale compreso tra 0 e il conteggio, quindi esegui:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()

139
Sfortunatamente skip () è piuttosto inefficiente poiché deve scansionare molti documenti. Inoltre, esiste una condizione di competizione se le righe vengono rimosse tra ottenere il conteggio ed eseguire la query.
mstearn,

6
Si noti che il numero casuale deve essere compreso tra 0 e il conteggio (esclusivo). Cioè, se hai 10 elementi, il numero casuale dovrebbe essere compreso tra 0 e 9. Altrimenti il ​​cursore potrebbe provare a saltare l'ultimo elemento e non verrebbe restituito nulla.
opaco

4
Grazie, ha funzionato perfettamente per i miei scopi. @mstearn, i tuoi commenti sia sull'efficienza che sulle condizioni di gara sono validi, ma per le raccolte in cui nessuna delle due cose (estrazione batch sul lato server una tantum in una raccolta in cui i record non vengono eliminati), questo è di gran lunga superiore a hacky (IMO) soluzione nel ricettario Mongo.
Michael Moussa,

4
cosa fa l'impostazione del limite su -1?
MonkeyBonkey,

@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Se numberToReturn è 0, il db utilizzerà la dimensione di ritorno predefinita. Se il numero è negativo, il database restituirà quel numero e chiuderà il cursore. "
Ceejayoz,

86

Aggiornamento per MongoDB 3.2

3.2 ha introdotto $ sample alla pipeline di aggregazione.

C'è anche un buon post sul blog sul su come metterlo in pratica.

Per versioni precedenti (risposta precedente)

Questa era in realtà una richiesta di funzionalità: http://jira.mongodb.org/browse/SERVER-533 ma è stata archiviata in "Non risolto".

Il ricettario ha un'ottima ricetta per selezionare un documento casuale da una raccolta: http://cookbook.mongodb.org/patterns/random-attribute/

Per parafrasare la ricetta, assegni numeri casuali ai tuoi documenti:

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

Quindi selezionare un documento casuale:

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

Interrogazione con entrambi $gteed $lteè necessario per trovare il documento con un numero casuale più vicino rand.

E ovviamente ti consigliamo di indicizzare sul campo casuale:

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

Se stai già eseguendo una query su un indice, rilascialo random: 1, aggiungilo e aggiungilo di nuovo.


7
Ed ecco un modo semplice per aggiungere il campo casuale a tutti i documenti della raccolta. function setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey,

8
Questo seleziona un documento a caso, ma se lo fai più di una volta, le ricerche non sono indipendenti. È più probabile che tu ottenga lo stesso documento due volte di seguito di quanto imporrebbero le probabilità casuali.
Carente il

12
Sembra una cattiva implementazione dell'hash circolare. È anche peggio di quanto dice la mancanza: persino una ricerca è distorta perché i numeri casuali non sono distribuiti uniformemente. Per farlo correttamente, avresti bisogno di un set di, diciamo, 10 numeri casuali per documento. Più numeri casuali usi per documento, più uniforme diventa la distribuzione dell'output.
Thomas

4
Il biglietto MongoDB JIRA è ancora vivo: jira.mongodb.org/browse/SERVER-533 Vai commento e vota se vuoi la funzione.
David J.

1
Prendi nota del tipo di avvertimento menzionato. Questo non funziona in modo efficiente con una piccola quantità di documenti. Dati due elementi con chiave casuale di 3 e 63. Il documento # 63 sarà scelto più frequentemente dove $gteè il primo. Soluzione alternativa stackoverflow.com/a/9499484/79201 funzionerebbe meglio in questo caso.
Ryan Schumacher,

56

Puoi anche utilizzare la funzione di indicizzazione geospaziale di MongoDB per selezionare i documenti "più vicini" a un numero casuale.

Innanzitutto, abilita l'indicizzazione geospaziale su una raccolta:

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

Per creare un gruppo di documenti con punti casuali sull'asse X:

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

Quindi puoi ottenere un documento casuale dalla raccolta in questo modo:

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

Oppure puoi recuperare diversi documenti più vicini a un punto casuale:

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

Ciò richiede solo una query e nessun controllo null, inoltre il codice è pulito, semplice e flessibile. È anche possibile utilizzare l'asse Y del geopoint per aggiungere una seconda dimensione di casualità alla query.


8
Mi piace questa risposta, è la più efficiente che abbia mai visto che non richiede un sacco di problemi sul lato server.
Tony Million

4
Questo è anche di parte per i documenti che hanno pochi punti nelle vicinanze.
Thomas

6
Questo è vero, e ci sono anche altri problemi: i documenti sono fortemente correlati alle loro chiavi casuali, quindi è altamente prevedibile quali documenti verranno restituiti come gruppo se si selezionano più documenti. Inoltre, i documenti vicini ai limiti (0 e 1) hanno meno probabilità di essere scelti. Quest'ultimo potrebbe essere risolto utilizzando il geomapping sferico, che si avvolge ai bordi. Tuttavia, dovresti vedere questa risposta come una versione migliorata della ricetta del libro di cucina, non come un perfetto meccanismo di selezione casuale. È abbastanza casuale per la maggior parte degli scopi.
Nico de Poel,

@NicodePoel, mi piace la tua risposta e il tuo commento! E ho un paio di domande per te: 1- Come fai a sapere che i punti vicini ai limiti 0 e 1 hanno meno probabilità di essere scelti, è basato su un terreno matematico ?, 2- Puoi approfondire di più sul geomapping sferico, come migliorerà la selezione casuale e come farlo in MongoDB? ... apprezzato!
securecurve,

Arricchisci la tua idea. Infine, ho un ottimo codice che è molto compatibile con CPU e RAM! Grazie
Qais Bsharat il

21

La seguente ricetta è un po 'più lenta della soluzione del libro di cucina mongo (aggiungi una chiave casuale su ogni documento), ma restituisce documenti casuali distribuiti in modo più uniforme. È un po 'meno uniformemente distribuito rispetto alla skip( random )soluzione, ma molto più veloce e più sicuro nel caso in cui i documenti vengano rimossi.

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;
}

Richiede anche di aggiungere un campo "casuale" casuale ai tuoi documenti, quindi non dimenticare di aggiungerlo quando li crei: potrebbe essere necessario inizializzare la tua raccolta come mostrato da Geoffrey

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

Risultati benchmark

Questo metodo è molto più veloce di skip() metodo (di ceejayoz) e genera documenti più casuali del metodo "libro di cucina" riportato da Michael:

Per una collezione con 1.000.000 di elementi:

  • Questo metodo richiede meno di un millisecondo sulla mia macchina

  • il skip()metodo richiede in media 180 ms

Il metodo del ricettario non farà mai prelevare un gran numero di documenti perché il loro numero casuale non li favorisce.

  • Questo metodo selezionerà tutti gli elementi in modo uniforme nel tempo.

  • Nel mio benchmark era solo il 30% più lento del metodo del libro di cucina.

  • la casualità non è perfetta al 100% ma è molto buona (e può essere migliorata se necessario)

Questa ricetta non è perfetta: la soluzione perfetta sarebbe una funzionalità integrata, come altri hanno notato.
Tuttavia, dovrebbe essere un buon compromesso per molti scopi.


10

Ecco un modo per usare i ObjectIdvalori predefiniti _ide un po 'di matematica e logica.

// 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];

Questa è la logica generale nella rappresentazione della shell e facilmente adattabile.

Quindi in punti:

  • Trova i valori chiave primaria minima e massima nella raccolta

  • Genera un numero casuale compreso tra i timestamp di tali documenti.

  • Aggiungi il numero casuale al valore minimo e trova il primo documento che è maggiore o uguale a quel valore.

Questo utilizza "padding" dal valore timestamp in "hex" per formare un ObjectIdvalore valido poiché è quello che stiamo cercando. L'uso di numeri interi come _idvalore è essenzialmente più semplice ma la stessa idea di base nei punti.


Ho una collezione di 300.000.000 di righe. Questa è l'unica soluzione che funziona ed è abbastanza veloce.
Nikos,

8

In Python usando pymongo:

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]

5
Vale la pena notare che internamente, questo utilizzerà skip and limit, proprio come molte altre risposte.
JohnnyHK,

La tua risposta è corretta Tuttavia, si prega di sostituire count()con estimated_document_count()come count()è deprecato in Mongdo v4.2.
user3848207

8

Ora puoi usare l'aggregato. Esempio:

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

Vedi il doc .


3
Nota: $ sample può ottenere lo stesso documento più di una volta
Saman Shafigh,

6

è difficile se non ci sono dati da cui separarsi. quali sono i campi _id? sono ID oggetto mongodb? In tal caso, potresti ottenere i valori più alti e più bassi:

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

quindi se supponi che gli ID siano distribuiti uniformemente (ma non lo sono, ma almeno è un inizio):

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

1
Qualche idea su come sarebbe in PHP? o almeno quale lingua hai usato sopra? è Python?
Marcin,

6

Usando Python (pymongo), funziona anche la funzione di aggregazione.

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

Questo approccio è molto più veloce dell'esecuzione di una query per un numero casuale (ad esempio collection.find ([random_int]), in particolare per le raccolte di grandi dimensioni.


5

Puoi scegliere un timestamp casuale e cercare il primo oggetto che è stato creato in seguito. Scansionerà solo un singolo documento, anche se non ti darà necessariamente una distribuzione 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)
    };
}();

Sarebbe facilmente possibile distorcere la data casuale per tenere conto della crescita del database superlineare.
Martin Nowak,

questo è il metodo migliore per raccolte molto grandi, funziona su O (1), unline skip () o count () usato qui nelle altre soluzioni
marmor

4

La mia soluzione su 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;
}

3

Per ottenere un numero determinato di documenti casuali senza duplicati:

  1. prima ottieni tutti gli ID
  2. ottenere la dimensione dei documenti
  3. loop che ottiene un indice casuale e salta i duplicati

    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)
                });
            });

2

Suggerirei di usare map / ridurre, dove usi la funzione map per emettere solo quando un valore casuale è al di sopra di una data probabilità.

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 funzione di riduzione di cui sopra funziona perché un solo tasto ('1') viene emesso dalla funzione mappa.

Il valore della "probabilità" è definito nell '"ambito", quando si richiama mapRreduce (...)

L'uso di mapReduce in questo modo dovrebbe essere utilizzabile anche su un db ridotto.

Se si desidera selezionare esattamente n di m documenti dal db, è possibile farlo in questo modo:

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

Dove "countTotal" (m) è il numero di documenti nel db e "countSubset" (n) è il numero di documenti da recuperare.

Questo approccio potrebbe dare alcuni problemi su database frammentati.


4
Fare una scansione completa della raccolta per restituire 1 elemento ... questa deve essere la tecnica meno efficiente per farlo.
Thomas

1
Il trucco è che è una soluzione generale per restituire un numero arbitrario di elementi casuali - nel qual caso sarebbe più veloce delle altre soluzioni quando si ottengono> 2 elementi casuali.
Torbenl,

2

Puoi scegliere _id casuale e restituire l'oggetto corrispondente:

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

Qui non è necessario dedicare spazio alla memorizzazione di numeri casuali nella raccolta.


1

Suggerirei di aggiungere un campo int casuale a ciascun oggetto. Quindi puoi semplicemente fare un

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

per scegliere un documento casuale. Assicurati di assicurarti che Index ({random_field: 1})


2
Se il primo record della tua raccolta ha un valore random_field relativamente elevato, non verrà restituito quasi sempre?
thehiatus il

2
thehaitus è corretto, lo farà - non è adatto a nessuno scopo
Heptic

7
Questa soluzione è completamente sbagliata, l'aggiunta di un numero casuale (immaginiamo tra 0 a 2 ^ 32-1) non garantisce alcuna buona distribuzione e l'uso di $ gte lo rende ancora peggio, a causa della tua selezione casuale non sarà nemmeno vicina a un numero pseudo-casuale. Suggerisco di non usare mai questo concetto.
Maximiliano Rios,

1

Quando mi sono trovato di fronte a una soluzione simile, ho fatto un passo indietro e ho scoperto che la richiesta commerciale era in realtà quella di creare una forma di rotazione dell'inventario presentato. In quel caso, ci sono opzioni molto migliori, che hanno risposte da motori di ricerca come Solr, non archivi di dati come MongoDB.

In breve, con il requisito di "ruotare in modo intelligente" il contenuto, ciò che dovremmo fare invece di un numero casuale in tutti i documenti è includere un modificatore di punteggio q personale. Per implementarlo da solo, supponendo che una piccola popolazione di utenti, sia possibile archiviare un documento per utente con ID prodotto, conteggio delle impressioni, conteggio dei click-through, data dell'ultima visualizzazione e qualsiasi altro fattore che l'azienda ritenga significativo per calcolare un punteggio q modificatore. Quando si recupera il set da visualizzare, in genere si richiedono più documenti dall'archivio dati di quelli richiesti dall'utente finale, quindi si applica il modificatore del punteggio q, si prende il numero di record richiesti dall'utente finale, quindi si randomizza la pagina dei risultati, un piccolo impostare, quindi semplicemente ordinare i documenti nel livello dell'applicazione (in memoria).

Se l'universo degli utenti è troppo grande, è possibile classificare gli utenti in gruppi di comportamenti e indicizzarli per gruppo di comportamenti anziché per utente.

Se l'universo dei prodotti è abbastanza piccolo, è possibile creare un indice per utente.

Ho trovato questa tecnica molto più efficiente, ma soprattutto più efficace nel creare un'esperienza pertinente e utile nell'uso della soluzione software.


1

nessuna delle soluzioni ha funzionato bene per me. specialmente quando ci sono molte lacune e il set è piccolo. questo ha funzionato molto bene per me (in php):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();

Specifica la lingua, ma non la libreria che stai utilizzando?
Benjamin,

Cordiali saluti, c'è una condizione di gara qui se un documento viene rimosso tra la prima e la terza linea. Inoltre find+ skipè piuttosto male, stai restituendo tutti i documenti solo per sceglierne uno: S.
Martin Konecny,


1

Il mio ordinamento / ordine PHP / MongoDB per soluzione RANDOM. Spero che questo aiuti chiunque.

Nota: nella mia raccolta MongoDB sono presenti ID numerici che fanno riferimento a un record del database MySQL.

Per prima cosa creo un array con 10 numeri generati casualmente

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

Nella mia aggregazione utilizzo l'operatore pipeline $ addField combinato con $ arrayElemAt e $ mod (modulo). L'operatore del modulo mi darà un numero compreso tra 0 e 9 che userò quindi per selezionare un numero dall'array con numeri generati casualmente.

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

Successivamente è possibile utilizzare l'ordinamento Pipeline.

    $aggregate[] = [
        '$sort' => [
            'random_sort' => 1
        ]
    ];

0

Se si dispone di una chiave ID semplice, è possibile archiviare tutti gli ID in un array, quindi selezionare un ID casuale. (Risposta di Ruby):

ids = @coll.find({},fields:{_id:1}).to_a
@coll.find(ids.sample).first

0

Usando Mappa / Riduci, puoi sicuramente ottenere un record casuale, ma non necessariamente in modo molto efficiente a seconda delle dimensioni della raccolta filtrata risultante con cui finisci per lavorare.

Ho testato questo metodo con 50.000 documenti (il filtro lo riduce a circa 30.000) e viene eseguito in circa 400 ms su un Intel i3 con 16 GB di RAM e un HDD 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 funzione Mappa crea semplicemente una matrice di ID di tutti i documenti che corrispondono alla query. Nel mio caso l'ho provato con circa 30.000 dei 50.000 documenti possibili.

La funzione Riduci semplicemente seleziona un numero intero casuale compreso tra 0 e il numero di elementi (-1) nella matrice, quindi restituisce quel _id dall'array.

400ms sembra molto tempo, e lo è davvero, se avessi cinquanta milioni di dischi anziché cinquantamila, questo potrebbe aumentare il sovraccarico al punto in cui diventa inutilizzabile in situazioni multiutente.

Esiste un problema aperto per MongoDB di includere questa funzione nel core ... https://jira.mongodb.org/browse/SERVER-533

Se questa selezione "casuale" fosse incorporata in una ricerca per indice invece di raccogliere gli ID in un array e quindi selezionarne uno, ciò aiuterebbe incredibilmente. (vai a votare!)


0

Funziona bene, è veloce, funziona con più documenti e non richiede il randcampo di popolamento , che alla fine si popolerà:

  1. aggiungi indice al campo .rand della tua raccolta
  2. usa trova e aggiorna, qualcosa come:
// 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. Come trovare record casuali nella domanda di mongodb è contrassegnato come duplicato di questa domanda. La differenza è che questa questione si chiede esplicitamente di singolo record come l'altra in modo esplicito su come ottenere documenti casuali s .


-2

Se stai usando mongoid, il wrapper da documento a oggetto, puoi fare quanto segue in Ruby. (Supponendo che il tuo modello sia utente)

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

Nel mio .irbrc, l'ho fatto

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

quindi nella console delle rotaie, ad esempio, posso fare

rando User
rando Article

per ottenere documenti a caso da qualsiasi raccolta.


1
Questo è terribilmente inefficiente in quanto leggerà l'intera raccolta in un array e quindi selezionerà un record.
JohnnyHK,

Ok, forse inefficiente, ma sicuramente conveniente. provare questo se la dimensione dei dati non è troppo grande
Zack Xu

3
Certo, ma la domanda originale era per una raccolta con 100 milioni di documenti, quindi questa sarebbe una pessima soluzione per quel caso!
JohnnyHK,

-2

puoi anche usare shuffle-array dopo aver eseguito la tua query

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

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


-7

Ciò che funziona in modo efficiente e affidabile è questo:

Aggiungi un campo chiamato "random" a ciascun documento e assegna un valore casuale ad esso, aggiungi un indice per il campo random e procedi come segue:

Supponiamo di avere una raccolta di collegamenti Web chiamati "collegamenti" e vogliamo un collegamento casuale da esso:

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

Per assicurarti che lo stesso link non venga visualizzato una seconda volta, aggiorna il suo campo casuale con un nuovo numero casuale:

db.links.update({random: Math.random()}, link)

2
perché aggiornare il database quando puoi semplicemente selezionare una chiave casuale diversa?
Jason S

È possibile che non si disponga di un elenco di tasti tra cui selezionare in modo casuale.
Mike,

Quindi devi ordinare l'intera collezione ogni volta? E i dischi sfortunati che hanno ottenuto grandi numeri casuali? Non saranno mai selezionati.
Fantius,

1
Devi farlo perché le altre soluzioni, in particolare quella suggerita nel libro MongoDB, non funzionano. Se la prima ricerca non riesce, la seconda ricerca restituisce sempre l'elemento con il valore casuale più piccolo. Se indicizzi in modo casuale in modo discendente, la prima query restituisce sempre l'elemento con il numero casuale più grande.
naufragio del

Aggiungere un campo in ciascun documento? Penso che non sia consigliabile.
CS_noob,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.