2011-12-14 16 views
16

Ho scoperto di recente che Mongo non ha l'equivalente SQL di "ORDER BY RAND()" nella sua sintassi del comando (https://jira.mongodb.org/browse/SERVER-533)Ordinare un set di risultati in modo casuale in mongo

Ho visto la raccomandazione allo http://cookbook.mongodb.org/patterns/random-attribute/ e, francamente, aggiungere un attributo casuale a un documento sembra un trucco. Questo non funzionerà perché questo pone un limite implicito a qualsiasi query che voglio randomizzare.

L'altro suggerimento ampiamente offerto è quello di scegliere un indice casuale da cui compensare. A causa dell'ordine in cui sono stati inseriti i miei documenti, ciò comporterà l'alfabetizzazione di uno dei campi stringa, che non sarà molto casuale per un utente del mio sito.

Ho un paio di idee su come potrei risolvere questo tramite codice, ma sento che mi manca una soluzione più ovvia e nativa. Qualcuno ha un pensiero o un'idea su come risolvere questo più elegantemente?

+2

C'è una [richiesta di funzionalità per ottenere elementi casuali da una raccolta] (https://jira.mongodb.org/browse/SERVER-533) nel Ticket tracker MongoDB. Se implementato in modo nativo, sarebbe probabilmente l'opzione più efficiente. (Se vuoi la funzione, vai a votare.) –

+0

Questa domanda è stata posta sotto molte forme qui su Stack Overflow. La domanda più popolare è [Record casuale da MongoDB] (http://stackoverflow.com/questions/2824157/random-record-from-mongodb) - ha buone risposte. Detto questo, penso che il modo migliore di pensare alla domanda sia non pensare di ottenere un documento a caso ma, piuttosto, randomizzare un set di risultati, proprio come hai chiesto tu! Vedi [Ordinando un set di risultati a caso in Mongo] (http://stackoverflow.com/questions/8500266/ordering-a-result-set-randomly-in-mongo) per quello. –

risposta

0

L'altro suggerimento ampiamente dato è quello di scegliere un indice casuale da cui compensare. A causa dell'ordine in cui sono stati inseriti i miei documenti, ciò comporterà l'alfabetizzazione di uno dei campi stringa, che non sarà molto casuale per un utente del mio sito.

Perché? Se hai 7.000 documenti e scegli tre offset casuali da 0 a 6999, i documenti scelti saranno casuali, anche se la raccolta stessa è ordinata alfabeticamente.

+1

La tua soluzione funziona davvero, ma richiede un sacco di sottoquery per contare le dimensioni della raccolta completa e quindi per estrarre manualmente gli offset casuali. Questo è decisamente fastidioso se voglio estrarre una grande quantità di record in ordine casuale. –

2

Ciò che si desidera non può essere fatto senza selezionare nessuna delle due soluzioni citate. Scegliere un offset casuale è un'idea orribile se la tua collezione diventa più grande di qualche migliaio di documenti. La ragione di ciò è che l'operazione skip (n) richiede tempo O (n). In altre parole, maggiore è l'offset casuale più lungo sarà la query.

L'aggiunta di un campo randomizzato al documento è, a mio parere, la soluzione meno hacky in cui è presente l'attuale set di funzionalità di MongoDB. Fornisce tempi di interrogazione stabili e ti dà qualche informazione su come la collezione è randomizzata (e ti permette di generare un nuovo valore casuale dopo ogni query attraverso un findAndModify per esempio). Inoltre, non capisco come questo imporrebbe un limite implicito alle tue query che fanno uso della randomizzazione.

+1

Aggiunge un limite implicito perché potrebbe esserci solo una certa quantità di documenti in qualsiasi numero generato casualmente - per esempio, se il numero casuale che disegno è 0.9111, ci sarà solo un certo numero di documenti che si qualificheranno per il criteri $ gte => 0.9111 –

+0

@Andy, questo è solo un limite della quantità di documenti che si adattano ai criteri è inferiore all'importo richiesto per l'applicazione. Se colpisci quel caso limite, puoi semplicemente integrare il set con una nuova query con un numero casuale appena generato. –

+0

@Andy, anche tu vorresti trovare un singolo documento per valore casuale piuttosto che il set a partire da 0.9111 se hai bisogno che il tuo set sia veramente casuale (ad esempio eviti il ​​caso in cui il set restituito da 0.9111 sia il 90% uguale a quello restituito usando 0,9222 per esempio) –

7

Sono d'accordo: la cosa più semplice da fare è installare un valore casuale nei tuoi documenti. Non è necessario che sia presente un intervallo di valori estremamente ampio: il numero scelto dipende dalla dimensione del risultato prevista per le query (1.000 - 1.000.000 interi distinti dovrebbero essere sufficienti per la maggior parte dei casi).

Quando si esegue la query, non preoccuparsi del campo casuale, ma indicizzarlo e utilizzarlo per ordinare. Poiché non vi è corrispondenza tra il numero casuale e il documento, è necessario ottenere risultati abbastanza casuali. Si noti che le collisioni probabilmente causeranno la restituzione dei documenti in ordine naturale.

Mentre questo è certamente un hack, si dispone di una molto facile via di fuga: data di MongoDB naturale assenza di schema, si può semplicemente fermare compreso il campo casuale una volta v'è il supporto per l'ordinamento casuale nel server. Se la dimensione è un problema, è possibile eseguire un processo batch per rimuovere il campo dai documenti esistenti. Non ci dovrebbe essere un cambiamento significativo nel codice del cliente se lo si progetta attentamente.

Un'opzione alternativa sarebbe pensare a lungo e duramente sul numero di risultati che saranno randomizzati e restituiti per una determinata query. Potrebbe non essere eccessivamente costoso fare semplicemente shuffling nel codice client (ad esempio, se si considerano solo i 10.000 post più recenti).

+1

Sì, in questo caso è davvero ragionevole prendere l'intera raccolta e fare tutto sul codice client, ecco cosa ho fatto. Semplicemente * sembra * come una funzionalità che dovrebbe essere nativa per l'archiviazione dei dati. –

+0

Non penso che la casualità sia un requisito molto comune per i database ed è un po 'difficile da implementare in modo efficiente in un tempo costante. –

+0

Aggiungo anche che l'impaginazione diventa piuttosto strana con un ordinamento casuale. – juanpaco

0

Si potrebbe inserire un campo id (il campo $ id non funzionerà perché non è un numero effettivo) utilizzare la matematica del modulo per ottenere un salto casuale. Se si hanno 10.000 record e si desideravano 10 risultati, si può scegliere un modulo tra 1 e 1000 a caso in modo casuale come 253 e quindi richiedere dove mod (id, 253) = 0 e questo è ragionevolmente veloce se l'ID è indicizzato. Quindi ordinare in modo casuale i 10 risultati del lato client. Certo, sono equamente distanziati invece che casuali, ma vicini a ciò che si desidera.

2

È possibile dare una prova - è veloce, lavora con documenti multipli e non richiede popolando rand campo all'inizio, che alla fine popolarsi:

  1. aggiungere indice .rand campo sul vostro raccolta
  2. utilizzo trovare e aggiornare, 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) 
    } 
}) 
0

Entrambe le opzioni sembra come hack non perfetto per me, a caso ha presentato una d avrà sempre lo stesso valore e skip restituirà gli stessi record per lo stesso numero.

Perché non usi un campo casuale per ordinare, poi salti casualmente, ammetto che è anche un trucco, ma nella mia esperienza dà un migliore senso di casualità.

Problemi correlati