sql >> Base de Datos >  >> NoSQL >> MongoDB

MongoDB Calcular valores de dos matrices, ordenar y limitar

El procesamiento actual es mapReduce

Si necesita ejecutar esto en el servidor y ordenar los mejores resultados y simplemente conservar los 100 mejores, entonces podría usar mapReduce para esto de la siguiente manera:

db.test.mapReduce(
    function() {
        var input = [0.1,0.3,0.4];
        var value = Array.sum(this.vals.map(function(el,idx) {
            return Math.abs( el - input[idx] )
        }));

        emit(null,{ "output": [{ "_id": this._id, "value": value }]});
    },
    function(key,values) {
        var output = [];

        values.forEach(function(value) {
            value.output.forEach(function(item) {
                output.push(item);
            });
        });

        output.sort(function(a,b) {
            return a.value < b.value;
        });

        return { "output": output.slice(0,100) };
    },
    { "out": { "inline": 1 } }
)

Entonces, la función del mapeador hace el cálculo y genera todo bajo la misma tecla, por lo que todos los resultados se envían al reductor. El resultado final estará contenido en una matriz en un solo documento de salida, por lo que es importante que todos los resultados se emitan con el mismo valor clave y que la salida de cada emisión sea en sí misma una matriz para que mapReduce pueda funcionar correctamente.

La clasificación y la reducción se realizan en el propio reductor, a medida que se inspecciona cada documento emitido, los elementos se colocan en una sola matriz temporal, se clasifican y se devuelven los mejores resultados.

Eso es importante, y solo la razón por la cual el emisor produce esto como una matriz, incluso si es un solo elemento al principio. MapReduce funciona procesando los resultados en "fragmentos", por lo que incluso si todos los documentos emitidos tienen la misma clave, no se procesan todos a la vez. Más bien, el reductor vuelve a colocar sus resultados en la cola de resultados emitidos para reducirlos hasta que solo quede un documento para esa clave en particular.

Estoy restringiendo la salida de "rebanada" aquí a 10 por brevedad de la lista, e incluyo las estadísticas para hacer un punto, ya que se pueden ver los 100 ciclos de reducción llamados en esta muestra de 10000:

{
    "results" : [
        {
            "_id" : null,
            "value" : {
                "output" : [
                    {
                        "_id" : ObjectId("56558d93138303848b496cd4"),
                        "value" : 2.2
                    },
                    {
                        "_id" : ObjectId("56558d96138303848b49906e"),
                        "value" : 2.2
                    },
                    {
                        "_id" : ObjectId("56558d93138303848b496d9a"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d93138303848b496ef2"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497861"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497b58"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497ba5"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d94138303848b497c43"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d95138303848b49842b"),
                        "value" : 2.1
                    },
                    {
                        "_id" : ObjectId("56558d96138303848b498db4"),
                        "value" : 2.1
                    }
                ]
            }
        }
    ],
    "timeMillis" : 1758,
    "counts" : {
            "input" : 10000,
            "emit" : 10000,
            "reduce" : 100,
            "output" : 1
    },
    "ok" : 1
}

Así que esta es una salida de documento único, en el formato mapReduce específico, donde el "valor" contiene un elemento que es una matriz del resultado ordenado y limitado.

El procesamiento futuro es agregado

Al momento de escribir, la última versión estable actual de MongoDB es 3.0, y carece de la funcionalidad para hacer posible su operación. Pero la próxima versión 3.2 presenta nuevos operadores que lo hacen posible:

db.test.aggregate([
    { "$unwind": { "path": "$vals", "includeArrayIndex": "index" }},
    { "$group": {
        "_id": "$_id",
        "result": {
            "$sum": {
                "$abs": {
                    "$subtract": [ 
                        "$vals", 
                        { "$arrayElemAt": [ { "$literal": [0.1,0.3,0.4] }, "$index" ] } 
                    ]
                }
            }
        }
    }},
    { "$sort": { "result": -1 } },
    { "$limit": 100 }
])

También limitando a los mismos 10 resultados por brevedad, obtienes un resultado como este:

{ "_id" : ObjectId("56558d96138303848b49906e"), "result" : 2.2 }
{ "_id" : ObjectId("56558d93138303848b496cd4"), "result" : 2.2 }
{ "_id" : ObjectId("56558d96138303848b498e31"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497c43"), "result" : 2.1 }
{ "_id" : ObjectId("56558d94138303848b497861"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499037"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b498db4"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496ef2"), "result" : 2.1 }
{ "_id" : ObjectId("56558d93138303848b496d9a"), "result" : 2.1 }
{ "_id" : ObjectId("56558d96138303848b499182"), "result" : 2.1 }

Esto es posible en gran parte gracias a $unwind siendo modificado para proyectar un campo en los resultados que contiene el índice de matriz, y también debido a $arrayElemAt que es un nuevo operador que puede extraer un elemento de matriz como un valor singular de un índice proporcionado.

Esto permite la "búsqueda" de valores por posición de índice desde su matriz de entrada para aplicar las matemáticas a cada elemento. La matriz de entrada es facilitada por el existente $literal operador entonces $arrayElemAt no se queja y lo reconoce como una matriz (parece ser un pequeño error en este momento, ya que otras funciones de matriz no tienen el problema con la entrada directa) y obtiene el valor de índice coincidente apropiado utilizando el campo "índice" producido por $unwind para comparar.

El cálculo lo realiza $subtract y por supuesto otro nuevo operador en $abs para cumplir con su funcionalidad. Además, dado que era necesario desenredar la matriz en primer lugar, todo esto se hace dentro de un $group etapa acumulando todos los miembros de la matriz por documento y aplicando la adición de entradas a través de $sum acumulador.

Finalmente, todos los documentos de resultados se procesan con $sort y luego $limit se aplica solo para devolver los mejores resultados.

Resumen

Incluso con la nueva funcionalidad a punto de estar disponible para el marco de agregación de MongoDB, es discutible qué enfoque es realmente más eficiente para obtener resultados. Esto se debe en gran parte a que aún existe la necesidad de $unwind el contenido de la matriz, que efectivamente produce una copia de cada documento por miembro de la matriz en la canalización para ser procesado, y eso generalmente genera una sobrecarga.

Entonces, aunque mapReduce es la única forma actual de hacer esto hasta una nueva versión, en realidad puede superar la declaración de agregación según la cantidad de datos que se procesarán, y a pesar del hecho de que el marco de agregación funciona en operadores codificados nativos en lugar de JavaScript traducido. operaciones.

Como con todas las cosas, siempre se recomienda realizar pruebas para ver qué caso se adapta mejor a sus propósitos y cuál ofrece el mejor rendimiento para su procesamiento esperado.

Muestra

Por supuesto, el resultado esperado para el documento de muestra proporcionado en la pregunta es 0.9 por las matemáticas aplicadas. Pero solo para mis propósitos de prueba, aquí hay una lista corta que se usa para generar algunos datos de muestra que quería al menos verificar que el código de mapReduce funcionaba como debería:

var bulk = db.test.initializeUnorderedBulkOp();

var x = 10000;

while ( x-- ) {
    var vals = [0,0,0];

    vals = vals.map(function(val) {
        return Math.round((Math.random()*10),1)/10;
    });

    bulk.insert({ "vals": vals });

    if ( x % 1000 == 0) {
        bulk.execute();
        bulk = db.test.initializeUnorderedBulkOp();
    }
}

Las matrices son valores de punto decimal único totalmente aleatorios, por lo que no hay mucha distribución en los resultados enumerados que di como muestra de salida.