Registro aleatorio de MongoDB

336

Estoy buscando obtener un registro aleatorio de un enorme (registro de 100 millones) mongodb.

¿Cuál es la forma más rápida y eficiente de hacerlo? Los datos ya están allí y no hay ningún campo en el que pueda generar un número aleatorio y obtener una fila aleatoria.

¿Alguna sugerencia?

Will M
fuente
2
Consulte también esta pregunta SO titulada "Ordenar un conjunto de resultados al azar en mongo" . Pensar en ordenar aleatoriamente un conjunto de resultados es una versión más general de esta pregunta, más poderosa y más útil.
David J.
11
Esta pregunta sigue apareciendo. La información más reciente se puede encontrar en la solicitud de función para obtener elementos aleatorios de una colección en el rastreador de tickets MongoDB. Si se implementa de forma nativa, probablemente sería la opción más eficiente. (Si quieres la función, ve a votarla)
David J.
¿Es esta una colección fragmentada?
Dylan Tong
3
@JohnnyHK ha dado la respuesta correcta a continuación: db.mycoll.aggregate ({$ muestra: {tamaño: 1}})
Florian
¿Alguien sabe cuánto más lento es esto que solo tomar el primer disco? Estoy debatiendo si vale la pena tomar una muestra aleatoria para hacer algo en lugar de hacerlo en orden.
David Kong

Respuestas:

248

A partir de la versión 3.2 de MongoDB, puede obtener N documentos aleatorios de una colección utilizando el $sampleoperador de canalización de agregación:

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

Si desea seleccionar los documentos aleatorios de un subconjunto filtrado de la colección, anteponga una $matchetapa a la tubería:

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

Como se señaló en los comentarios, cuando sizees mayor que 1, puede haber duplicados en la muestra del documento devuelto.

JohnnyHK
fuente
12
Esta es una buena manera, pero recuerde que NO garantiza que no haya copias del mismo objeto en la muestra.
Matheus Araujo
10
@MatheusAraujo, lo que no importará si quieres un disco pero un buen punto de todos modos
Toby
3
No para ser pedante, pero la pregunta no especifica una versión de MongoDB, por lo que supongo que tener la versión más reciente es razonable.
dalanmiller
2
@Nepoxx Vea los documentos sobre el procesamiento involucrado.
JohnnyHK
2
@brycejl Eso tendría el defecto fatal de no coincidir con nada si la etapa $ sample no seleccionara ningún documento coincidente.
JohnnyHK
115

Haga un recuento de todos los registros, genere un número aleatorio entre 0 y el recuento, y luego haga:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
ceejayoz
fuente
139
Desafortunadamente, skip () es bastante ineficiente ya que tiene que escanear tantos documentos. Además, hay una condición de carrera si las filas se eliminan entre obtener el recuento y ejecutar la consulta.
mstearn
66
Tenga en cuenta que el número aleatorio debe estar entre 0 y el recuento (exclusivo). Es decir, si tiene 10 elementos, el número aleatorio debe estar entre 0 y 9. De lo contrario, el cursor podría intentar saltarse el último elemento y no se devolverá nada.
mate
44
Gracias, funcionó perfectamente para mis propósitos. @mstearn, sus comentarios sobre la eficiencia y las condiciones de carrera son válidos, pero para las colecciones en las que ninguna de las dos cuestiones (extracto por lotes del servidor de una sola vez en una colección donde los registros no se eliminan), esto es muy superior al hacky (IMO) solución en el Mongo Cookbook.
Michael Moussa
44
¿Qué significa establecer el límite en -1?
MonkeyBonkey
@MonkeyBonkey docs.mongodb.org/meta-driver/latest/legacy/… "Si numberToReturn es 0, la base de datos utilizará el tamaño de retorno predeterminado. Si el número es negativo, la base de datos devolverá ese número y cerrará el cursor. "
ceejayoz
86

Actualización para MongoDB 3.2

3.2 introdujo $ sample en la canalización de agregación.

También hay una buena publicación de blog sobre cómo ponerla en práctica.

Para versiones anteriores (respuesta anterior)

En realidad, esta fue una solicitud de función: http://jira.mongodb.org/browse/SERVER-533 pero se archivó en "No se solucionará".

El libro de cocina tiene una muy buena receta para seleccionar un documento aleatorio de una colección: http://cookbook.mongodb.org/patterns/random-attribute/

Parafraseando la receta, asigna números aleatorios a sus documentos:

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

Luego seleccione un documento aleatorio:

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

Consulta con ambos $gtey $ltees necesario encontrar el documento con un número aleatorio más cercano rand.

Y, por supuesto, querrá indexar en el campo aleatorio:

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

Si ya está consultando un índice, simplemente suéltelo random: 1, agréguelo y agréguelo nuevamente.

Miguel
fuente
77
Y aquí hay una manera simple de agregar el campo aleatorio a cada documento de la colección. function setRandom () {db.topics.find (). forEach (function (obj) {obj.random = Math.random (); db.topics.save (obj);}); } db.eval (setRandom);
Geoffrey
8
Esto selecciona un documento al azar, pero si lo hace más de una vez, las búsquedas no son independientes. Es más probable que obtenga el mismo documento dos veces seguidas de lo que dicta la posibilidad aleatoria.
Lacker
12
Parece una mala implementación del hashing circular. Es incluso peor de lo que dice lacker: incluso una búsqueda está sesgada porque los números aleatorios no están distribuidos de manera uniforme. Para hacer esto correctamente, necesitaría un conjunto de, por ejemplo, 10 números aleatorios por documento. Cuantos más números aleatorios use por documento, más uniforme será la distribución de salida.
Thomas
44
El boleto MongoDB JIRA todavía está vivo: jira.mongodb.org/browse/SERVER-533 Ve a comentar y vota si quieres la función.
David J.
1
Tome nota del tipo de advertencia mencionada. Esto no funciona de manera eficiente con una pequeña cantidad de documentos. Dados dos elementos con clave aleatoria de 3 y 63. El documento # 63 se elegirá con mayor frecuencia donde $gteestá primero. La solución alternativa stackoverflow.com/a/9499484/79201 funcionaría mejor en este caso.
Ryan Schumacher
56

También puede usar la función de indexación geoespacial de MongoDB para seleccionar los documentos 'más cercanos' a un número aleatorio.

Primero, habilite la indexación geoespacial en una colección:

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

Para crear un montón de documentos con puntos aleatorios en el eje X:

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

Entonces puede obtener un documento aleatorio de la colección como este:

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

O puede recuperar varios documentos más cercanos a un punto aleatorio:

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

Esto requiere solo una consulta y no verificaciones nulas, además el código es limpio, simple y flexible. Incluso podría usar el eje Y del geopunto para agregar una segunda dimensión de aleatoriedad a su consulta.

Nico de Poel
fuente
8
Me gusta esta respuesta, es la más eficiente que he visto y no requiere un montón de desorden sobre el lado del servidor.
Tony Million
44
Esto también está sesgado hacia los documentos que tienen pocos puntos en su vecindad.
Thomas
66
Eso es cierto, y también hay otros problemas: los documentos están fuertemente correlacionados en sus claves aleatorias, por lo que es altamente predecible qué documentos se devolverán como un grupo si selecciona varios documentos. Además, es menos probable que se elijan documentos cercanos a los límites (0 y 1). Esto último podría resolverse mediante el uso de geomapping esférico, que se envuelve en los bordes. Sin embargo, debería ver esta respuesta como una versión mejorada de la receta del libro de cocina, no como un mecanismo de selección aleatorio perfecto. Es lo suficientemente aleatorio para la mayoría de los propósitos.
Nico de Poel
@NicodePoel, ¡me gusta tu respuesta y tu comentario! Y tengo un par de preguntas para usted: 1- ¿Cómo sabe que los puntos cercanos a los límites 0 y 1 tienen menos probabilidades de ser elegidos, se basa en algún fundamento matemático ?, 2- ¿Puede elaborar más sobre geomapping esférico, ¿Cómo mejorará la selección aleatoria y cómo hacerlo en MongoDB? ... apreciado!
securecurve
Aprecia tu idea. Finalmente, tengo un gran código que es muy compatible con CPU y RAM. Gracias
Qais Bsharat
21

La siguiente receta es un poco más lenta que la solución de libro de cocina mongo (agregue una clave aleatoria en cada documento), pero devuelve documentos aleatorios distribuidos de manera más uniforme. Está un poco menos distribuido que la skip( random )solución, pero es mucho más rápido y más seguro en caso de que se eliminen los documentos.

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

También requiere que agregue un campo aleatorio "aleatorio" a sus documentos, así que no olvide agregar esto cuando los cree: es posible que deba inicializar su colección como lo muestra Geoffrey

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

Resultados de referencia

Este método es mucho más rápido que el skip()método (de ceejayoz) y genera documentos aleatorios más uniformes que el método del "libro de cocina" reportado por Michael:

Para una colección con 1,000,000 de elementos:

  • Este método toma menos de un milisegundo en mi máquina

  • el skip()método tarda 180 ms en promedio

El método del libro de cocina hará que nunca se recojan grandes cantidades de documentos porque su número aleatorio no los favorece.

  • Este método elegirá todos los elementos de manera uniforme con el tiempo.

  • En mi punto de referencia fue solo un 30% más lento que el método del libro de cocina.

  • la aleatoriedad no es 100% perfecta pero es muy buena (y se puede mejorar si es necesario)

Esta receta no es perfecta: la solución perfecta sería una característica incorporada como otros han señalado.
Sin embargo, debería ser un buen compromiso para muchos propósitos.

spam_eggs
fuente
10

Aquí hay una manera de usar los ObjectIdvalores predeterminados para _idy un poco de matemática y lógica.

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

Esa es la lógica general en la representación de shell y fácilmente adaptable.

Entonces en puntos:

  • Encuentre los valores de clave primaria mínima y máxima en la colección

  • Genere un número aleatorio que se encuentre entre las marcas de tiempo de esos documentos.

  • Agregue el número aleatorio al valor mínimo y encuentre el primer documento que sea mayor o igual a ese valor.

Esto usa "relleno" del valor de marca de tiempo en "hexadecimal" para formar un ObjectIdvalor válido ya que eso es lo que estamos buscando. Usar enteros como _idvalor es esencialmente más simple pero la misma idea básica en los puntos.

Blakes Seven
fuente
Tengo una colección de 300 000 000 líneas. Esta es la única solución que funciona y es lo suficientemente rápida.
Nikos
8

En Python usando pymongo:

import random

def get_random_doc():
    count = collection.count()
    return collection.find()[random.randrange(count)]
Jabba
fuente
55
Vale la pena señalar que internamente, esto usará saltar y limitar, al igual que muchas de las otras respuestas.
JohnnyHK
Tu respuesta es correcta. Sin embargo, reemplace count()con estimated_document_count()que count()está en desuso en Mongdo v4.2.
user3848207
8

Ahora puedes usar el agregado. Ejemplo:

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

Ver el doc .

dbam
fuente
3
Nota: $ sample puede obtener el mismo documento más de una vez
Saman Shafigh
6

es difícil si no hay datos para desconectar. ¿Cuáles son los campos _id? ¿son identificaciones de objeto mongodb? Si es así, podría obtener los valores más altos y más bajos:

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

entonces, si asume que los id están distribuidos uniformemente (pero no lo están, pero al menos es un comienzo):

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.
fuente
1
¿Alguna idea de cómo se vería eso en PHP? o al menos qué idioma has usado anteriormente? ¿es Python?
Marcin
6

Usando Python (pymongo), la función de agregado también funciona.

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

Este enfoque es mucho más rápido que ejecutar una consulta para un número aleatorio (por ejemplo, collection.find ([random_int]). Este es especialmente el caso de grandes colecciones.

Daniel
fuente
5

Puede elegir una marca de tiempo aleatoria y buscar el primer objeto que se creó después. Solo escaneará un solo documento, aunque no necesariamente le proporciona una distribución 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
fuente
Sería fácilmente posible sesgar la fecha aleatoria para tener en cuenta el crecimiento superlineal de la base de datos.
Martin Nowak
este es el mejor método para colecciones muy grandes, funciona en O (1), unline skip () o count () utilizado en las otras soluciones aquí
marmor
4

Mi solución en 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
fuente
3

Para obtener un número determinado de documentos aleatorios sin duplicados:

  1. primero obtener todos los identificadores
  2. obtener el tamaño de los documentos
  3. bucle que obtiene índice aleatorio y omisión duplicada

    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
fuente
2

Sugeriría usar map / reduce, donde usa la función de mapa para emitir solo cuando un valor aleatorio está por encima de una probabilidad dada.

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 función reducef anterior funciona porque solo se emite una tecla ('1') desde la función de mapa.

El valor de la "probabilidad" se define en el "alcance", cuando se invoca mapRreduce (...)

El uso de mapReduce como este también debería ser utilizable en una base de datos fragmentada.

Si desea seleccionar exactamente n de m documentos de la base de datos, puede hacerlo así:

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

Donde "countTotal" (m) es el número de documentos en la base de datos, y "countSubset" (n) es el número de documentos a recuperar.

Este enfoque puede dar algunos problemas en las bases de datos fragmentadas.

torbenl
fuente
44
Hacer una exploración de recopilación completa para devolver 1 elemento ... esta debe ser la técnica menos eficiente para hacerlo.
Thomas
1
El truco es que es una solución general para devolver un número arbitrario de elementos aleatorios, en cuyo caso sería más rápido que las otras soluciones cuando se obtienen> 2 elementos aleatorios.
torbenl
2

Puede elegir _id aleatorio y devolver el objeto correspondiente:

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

Aquí no necesita gastar espacio para almacenar números aleatorios en la colección.

Vijay13
fuente
1

Sugeriría agregar un campo int aleatorio a cada objeto. Entonces puedes hacer un

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

para elegir un documento al azar. Solo asegúrese de asegurar Index ({random_field: 1})

mstearn
fuente
2
Si el primer registro de su colección tiene un valor de campo aleatorio relativamente alto, ¿no se devolverá casi todo el tiempo?
thehiatus
2
thehaitus es correcto, lo hará - no es adecuado para ningún propósito
Heptic
77
Esta solución es completamente incorrecta, agregar un número aleatorio (imaginemos entre 0 a 2 ^ 32-1) no garantiza una buena distribución y el uso de $ gte lo hace aún peor, debido a que su selección aleatoria ni siquiera estará cerca a un número pseudoaleatorio. Sugiero no usar este concepto nunca.
Maximiliano Rios
1

Cuando me enfrenté a una solución similar, retrocedí y descubrí que la solicitud comercial era en realidad para crear alguna forma de rotación del inventario que se presentaba. En ese caso, hay opciones mucho mejores, que tienen respuestas de motores de búsqueda como Solr, no almacenes de datos como MongoDB.

En resumen, con el requisito de "rotar inteligentemente" el contenido, lo que deberíamos hacer en lugar de un número aleatorio en todos los documentos es incluir un modificador de puntuación q personal. Para implementar esto usted mismo, suponiendo una pequeña población de usuarios, puede almacenar un documento por usuario que tenga el ID del producto, el recuento de impresiones, el recuento de clics, la fecha de la última visita y cualquier otro factor que la empresa considere significativo para calcular un puntaje q modificador Al recuperar el conjunto para mostrar, normalmente solicita más documentos del almacén de datos que los solicitados por el usuario final, luego aplica el modificador de puntuación q, toma el número de registros solicitados por el usuario final, luego aleatoriza la página de resultados, un pequeño establecer, así que simplemente ordene los documentos en la capa de aplicación (en la memoria).

Si el universo de usuarios es demasiado grande, puede clasificar a los usuarios en grupos de comportamiento e indexarlos por grupo de comportamiento en lugar de usuario.

Si el universo de productos es lo suficientemente pequeño, puede crear un índice por usuario.

He descubierto que esta técnica es mucho más eficiente, pero más importante, más efectiva para crear una experiencia relevante y valiosa de usar la solución de software.

paegun
fuente
1

ninguna de las soluciones funcionó bien para mí. especialmente cuando hay muchos huecos y el conjunto es pequeño. esto funcionó muy bien para mí (en php):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();
Mantas Karanauskas
fuente
¿Especificas el idioma, pero no la biblioteca que estás usando?
Benjamin
Para su información, hay una condición de carrera aquí si se elimina un documento entre la primera y la tercera línea. Además, find+ skipes bastante malo, está devolviendo todos los documentos solo para elegir uno: S.
Martin Konecny
1

Mi ordenación / orden PHP / MongoDB por solución RANDOM. Espero que esto ayude a cualquiera.

Nota: Tengo identificaciones numéricas dentro de mi colección MongoDB que se refieren a un registro de base de datos MySQL.

Primero creo una matriz con 10 números generados aleatoriamente

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

En mi agregación, uso el operador de canalización $ addField combinado con $ arrayElemAt y $ mod (módulo). El operador de módulo me dará un número de 0 a 9 que luego usaré para elegir un número de la matriz con números generados al azar.

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

Después de eso, puede usar el tipo Pipeline.

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

Si tiene una clave de identificación simple, puede almacenar todas las identificaciones en una matriz y luego elegir una identificación aleatoria. (Respuesta de Ruby):

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

Usando Map / Reduce, ciertamente puede obtener un registro aleatorio, pero no necesariamente de manera muy eficiente dependiendo del tamaño de la colección filtrada resultante con la que termina trabajando.

He probado este método con 50,000 documentos (el filtro lo reduce a aproximadamente 30,000), y se ejecuta en aproximadamente 400 ms en un Intel i3 con 16 GB de RAM y 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 función de mapa simplemente crea una matriz de id de todos los documentos que coinciden con la consulta. En mi caso, probé esto con aproximadamente 30,000 de los 50,000 documentos posibles.

La función Reducir simplemente elige un número entero aleatorio entre 0 y el número de elementos (-1) en la matriz, y luego devuelve ese _id de la matriz.

400 ms parece mucho tiempo, y realmente lo es, si tuviera cincuenta millones de registros en lugar de cincuenta mil, esto puede aumentar la sobrecarga hasta el punto en que se vuelva inutilizable en situaciones de múltiples usuarios.

Hay un problema abierto para que MongoDB incluya esta característica en el núcleo ... https://jira.mongodb.org/browse/SERVER-533

Si esta selección "aleatoria" se integrara en una búsqueda de índice en lugar de recopilar identificadores en una matriz y luego seleccionar uno, esto sería de gran ayuda. (¡Ve a votar!)

doble hélice
fuente
0

Esto funciona bien, es rápido, funciona con varios documentos y no requiere un randcampo de relleno, que eventualmente se completará:

  1. agregue índice al campo .rand en su colección
  2. use buscar y actualizar, algo 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)
  }
})

PD. Cómo encontrar registros aleatorios en la pregunta mongodb está marcado como duplicado de esta pregunta. La diferencia es que esta pregunta se refiere explícitamente de registro único como el otro de manera explícita acerca de cómo obtener documentos al azar s .

Mirek Rusin
fuente
-2

Si está usando mongoid, el contenedor de documento a objeto, puede hacer lo siguiente en Ruby. (Suponiendo que su modelo es Usuario)

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

En mi .irbrc, tengo

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

así que en la consola de rails, puedo hacer, por ejemplo,

rando User
rando Article

para obtener documentos al azar de cualquier colección.

Zack Xu
fuente
1
Esto es terriblemente ineficiente, ya que leerá toda la colección en una matriz y luego seleccionará un registro.
JohnnyHK
Ok, quizás ineficiente, pero seguramente conveniente. intente esto si su tamaño de datos no es demasiado grande
Zack Xu
3
Claro, pero la pregunta original era para una colección con 100 millones de documentos, ¡así que esta sería una muy mala solución para ese caso!
JohnnyHK
-2

También puede usar shuffle-array después de ejecutar su consulta

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

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

rabie jegham
fuente
-7

Lo que funciona de manera eficiente y confiable es esto:

Agregue un campo llamado "aleatorio" a cada documento y asígnele un valor aleatorio, agregue un índice para el campo aleatorio y proceda de la siguiente manera:

Supongamos que tenemos una colección de enlaces web llamados "enlaces" y queremos un enlace aleatorio de ella:

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

Para asegurarse de que el mismo enlace no aparezca por segunda vez, actualice su campo aleatorio con un nuevo número aleatorio:

db.links.update({random: Math.random()}, link)
Tren descarrilado
fuente
2
¿Por qué actualizar la base de datos cuando puede seleccionar una clave aleatoria diferente?
Jason S
Es posible que no tenga una lista de las teclas para seleccionar al azar.
Mike
¿Entonces tienes que ordenar toda la colección cada vez? ¿Y qué hay de los registros desafortunados que obtuvieron grandes números aleatorios? Nunca serán seleccionados.
Fantius
1
Tiene que hacer esto porque las otras soluciones, particularmente la sugerida en el libro de MongoDB, no funcionan. Si el primer hallazgo falla, el segundo hallazgo siempre devuelve el elemento con el valor aleatorio más pequeño. Si indexa aleatoriamente de forma descendente, la primera consulta siempre devuelve el elemento con el mayor número aleatorio.
trainwreck
¿Agregar un campo en cada documento? Creo que no es aconsejable.
CS_noob