2011-12-14 54 views
16

Recientemente descubrí que Mongo no tiene equivalente en SQL a "ORDER BY RAND()" en su sintaxis de comando (https://jira.mongodb.org/browse/SERVER-533)Ordenar un conjunto de resultados aleatoriamente en mongo

He visto la recomendación en http://cookbook.mongodb.org/patterns/random-attribute/ y, francamente, agregar un atributo aleatorio a un documento parece un truco. Esto no funcionará porque esto impone un límite implícito a cualquier consulta dada que quiera aleatorizar.

La otra sugerencia ampliamente aceptada es elegir un índice aleatorio para compensar. Debido al orden en que se insertaron mis documentos, dará como resultado que uno de los campos de cadena esté alfabetizado, lo que no parecerá muy aleatorio para un usuario de mi sitio.

Tengo un par de ideas sobre cómo podría resolver este código de acceso, pero siento que me falta una solución más obvia y nativa. ¿Alguien tiene un pensamiento o idea sobre cómo resolver esto de manera más elegante?

+2

Hay una [solicitud de función para obtener elementos aleatorios de una colección] (https://jira.mongodb.org/browse/SERVER-533) en el registrador de boletos MongoDB. Si se implementa de forma nativa, probablemente sea la opción más eficiente. (Si desea la función, vaya a votarla). –

+0

Esta pregunta se ha formulado de muchas formas aquí en Stack Overflow. La pregunta más popular es [Registro aleatorio de MongoDB] (http://stackoverflow.com/questions/2824157/random-record-from-mongodb) - tiene buenas respuestas. Dicho esto, creo que la mejor manera de pensar sobre la cuestión es no pensar en obtener un documento aleatorio sino, más bien, aleatorizar un conjunto de resultados, ¡tal como lo pidió! Ver [Ordenar un conjunto de resultados aleatoriamente en Mongo] (http://stackoverflow.com/questions/8500266/ordering-a-result-set-randomly-in-mongo) para eso. –

Respuesta

0

La otra sugerencia ampliamente recomendada es elegir un índice aleatorio para compensar. Debido al orden en que se insertaron mis documentos, dará como resultado que uno de los campos de cadena esté alfabetizado, lo que no parecerá muy aleatorio para un usuario de mi sitio.

¿Por qué? Si tiene 7.000 documentos y elige tres desplazamientos aleatorios de 0 a 6999, los documentos elegidos serán aleatorios, incluso si la colección en sí está ordenada alfabéticamente.

+1

Su solución realmente funciona, pero requiere una gran cantidad de subconsultas para contar el tamaño de la colección completa y luego obtener manualmente las compensaciones aleatorias. Esto es definitivamente problemático si quiero extraer una gran cantidad de registros en orden aleatorio. –

2

Lo que quiere no se puede hacer sin elegir cualquiera de las dos soluciones que menciona. Escoger un desplazamiento aleatorio es una idea horrible si su colección llega a ser más grande que unos pocos miles de documentos. La razón de esto es que la operación de omisión (n) requiere O (n) tiempo. En otras palabras, cuanto mayor sea el desplazamiento aleatorio, más tiempo tardará la consulta.

Agregar un campo al azar al documento es, en mi opinión, la solución menos hacky que se le da el conjunto de características actual de MongoDB. Proporciona tiempos de consulta estables y le da algo de opinión sobre cómo se aleatoriza la colección (y le permite generar un nuevo valor aleatorio después de cada consulta a través de un findAndModify, por ejemplo). Tampoco entiendo cómo esto podría imponer un límite implícito en sus consultas que hacen uso de la aleatorización.

+1

Agrega un límite implícito porque solo puede haber una cierta cantidad de documentos en cualquier número generado aleatoriamente; por ejemplo, si el número aleatorio que dibujo es 0.9111, solo habrá una cierta cantidad de documentos que calificarán para el criteria $ gte => 0.9111 –

+0

@Andy, eso es solo un límite de la cantidad de documentos que se ajustan a los criterios es menor que la cantidad que necesita para su aplicación. Si llegas a ese borde, simplemente complementas tu conjunto con una nueva consulta con un número aleatorio recién generado. –

+0

@Andy, también desearía encontrar un único documento por valor aleatorio en lugar del conjunto que comienza en 0.9111 si necesita que su conjunto sea verdaderamente aleatorio (por ejemplo, evite el caso en que el conjunto devuelto por 0.9111 sea 90% el mismo que que devolvió usando 0.9222, por ejemplo) –

7

Tengo que estar de acuerdo: lo más fácil es instalar un valor aleatorio en sus documentos. Tampoco es necesario que exista una gama de valores tremendamente amplia: el número que elija depende del tamaño del resultado esperado para sus consultas (1,000 - 1,000,000 enteros distintos deberían ser suficientes para la mayoría de los casos).

Cuando ejecuta su consulta, no se preocupe por el campo aleatorio; en su lugar, indexe y utilícelo para ordenar. Como no existe correspondencia entre el número aleatorio y el documento, debe obtener resultados bastante aleatorios. Tenga en cuenta que las colisiones probablemente darán como resultado que los documentos se devuelvan en orden natural.

Si bien esto es sin duda un corte, usted tiene una ruta de escape muy fácil: dada la naturaleza sin esquema de MongoDB, puede simplemente dejar de incluir el campo al azar una vez que no hay soporte para ordenar al azar en el servidor. Si el tamaño es un problema, puede ejecutar un trabajo por lotes para eliminar el campo de los documentos existentes. No debería haber un cambio significativo en su código de cliente si lo diseña cuidadosamente.

Una opción alternativa sería pensar detenidamente sobre la cantidad de resultados que se aleatorizarán y devolverán para una consulta determinada. Puede que no sea excesivamente caro simplemente mezclar en el código del cliente (es decir, si solo considera las 10,000 publicaciones más recientes).

+1

Sí, en este caso es razonable tomar toda la colección y hacerlo todo en el código del cliente, así que eso es lo que terminé haciendo. Simplemente * se siente * como una funcionalidad que debería ser nativa del almacenamiento de datos. –

+0

No creo que la aleatoriedad sea un requisito muy común para las bases de datos y es un poco complicado implementarlo de manera eficiente en tiempo constante. –

+0

También agregaría que la paginación se vuelve bastante extraña con un orden aleatorio. – juanpaco

0

Se podría insertar un campo de identificación (el campo $ id no funcionará porque no es un número real) use el módulo matemático para obtener un salto al azar. Si tiene 10.000 registros y desea 10 resultados, puede elegir un módulo entre 1 y 1000 sucH al azar como 253 y luego solicitar donde mod (id, 253) = 0 y esto es razonablemente rápido si se indexa id. Luego, ordena al azar los 10 resultados del lado del cliente. Claro que están espaciados uniformemente en lugar de ser verdaderamente aleatorios, pero están cerca de lo que se desea.

2

Puede probar esto - es rápido, trabaja con varios documentos y no requiere poblar rand campo al comienzo, que con el tiempo se rellenará en sí:

  1. agregar índice a .rand campo en su colección
  2. utilización encontrar y refrescar, algo así como:
// 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

las dos opciones parece como hacks no perfectas para mí, al azar interpuso una d siempre tendrá el mismo valor y el salto devolverá los mismos registros para un mismo número.

¿Por qué no utiliza un campo aleatorio para ordenar y omitir al azar, admito que también es un truco, pero en mi experiencia da una mejor sensación de aleatoriedad.

Cuestiones relacionadas