MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

MongoDB Calcola i valori da due array, Ordina e Limit

L'elaborazione corrente è mapReduce

Se hai bisogno di eseguirlo sul server e ordinare i risultati migliori e mantenere solo i primi 100, puoi usare mapReduce per questo in questo modo:

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

Quindi la funzione mapper esegue il calcolo e l'output è tutto sotto la stessa chiave, quindi tutti i risultati vengono inviati al riduttore. L'output finale sarà contenuto in un array in un unico documento di output, quindi è importante che tutti i risultati vengano emessi con lo stesso valore chiave e che l'output di ogni emit sia esso stesso un array in modo che mapReduce possa funzionare correttamente.

L'ordinamento e la riduzione vengono eseguiti nel riduttore stesso, poiché ogni documento emesso viene ispezionato, gli elementi vengono inseriti in un unico array temporaneo, ordinati e vengono restituiti i risultati principali.

Questo è importante, ed è proprio il motivo per cui l'emettitore lo produce come un array anche se inizialmente un singolo elemento. MapReduce funziona elaborando i risultati in "blocchi", quindi anche se tutti i documenti emessi hanno la stessa chiave, non vengono elaborati tutti in una volta. Piuttosto, il riduttore rimette i suoi risultati nella coda dei risultati emessi per essere ridotti fino a quando non rimane solo un singolo documento per quella particolare chiave.

Sto limitando l'output "slice" qui a 10 per brevità dell'elenco e includo le statistiche per sottolineare un punto, poiché è possibile vedere i 100 cicli di riduzione chiamati su questo campione di 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
}

Quindi questo è un unico output di documento, nel formato mapReduce specifico, in cui il "valore" contiene un elemento che è un array del risultato ordinato e limitato.

L'elaborazione futura è aggregata

Al momento della scrittura, l'ultima versione stabile di MongoDB è la 3.0 e questa non ha le funzionalità per rendere possibile l'operazione. Ma la prossima versione 3.2 introduce nuovi operatori che lo rendono possibile:

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

Limitandoti anche agli stessi 10 risultati per brevità, ottieni un output in questo modo:

{ "_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 }

Ciò è possibile in gran parte grazie a $unwind essere modificato per proiettare un campo nei risultati che contiene l'indice dell'array e anche a causa di $arrayElemAt che è un nuovo operatore in grado di estrarre un elemento dell'array come valore singolare da un indice fornito.

Ciò consente la "ricerca" di valori in base alla posizione dell'indice dall'array di input per applicare la matematica a ciascun elemento. L'array di input è facilitato dall'esistente $literal operatore quindi $arrayElemAt non si lamenta e lo riconosce come un array, (sembra essere un piccolo bug al momento, poiché altre funzioni di array non hanno il problema con l'input diretto) e ottiene il valore dell'indice corrispondente appropriato utilizzando il campo "indice" prodotto da $unwind per confronto.

I calcoli vengono eseguiti da $subtract e ovviamente un altro nuovo operatore in $abs per soddisfare la tua funzionalità. Inoltre, poiché in primo luogo era necessario svolgere l'array, tutto ciò viene eseguito all'interno di un $group fase accumulando tutti i membri dell'array per documento e applicando l'aggiunta di voci tramite $sum accumulatore.

Infine tutti i documenti dei risultati vengono elaborati con $sort e poi $limit viene applicato per restituire solo i risultati migliori.

Riepilogo

Anche con la nuova funzionalità che sta per essere disponibile per il framework di aggregazione per MongoDB, è discutibile quale approccio sia effettivamente più efficiente per i risultati. Ciò è dovuto in gran parte alla necessità di $unwind il contenuto dell'array, che produce effettivamente una copia di ogni documento per ogni membro dell'array nella pipeline da elaborare, e che generalmente provoca un sovraccarico.

Quindi, sebbene mapReduce sia l'unico modo attuale per farlo fino a una nuova versione, potrebbe effettivamente superare l'istruzione di aggregazione a seconda della quantità di dati da elaborare e nonostante il framework di aggregazione funzioni su operatori codificati nativi piuttosto che JavaScript tradotto operazioni.

Come per tutte le cose, si consiglia sempre di eseguire dei test per vedere quale custodia si adatta meglio ai tuoi scopi e quale offre le migliori prestazioni per l'elaborazione prevista.

Campione

Ovviamente il risultato atteso per il documento di esempio fornito nella domanda è 0.9 dalla matematica applicata. Ma solo per i miei scopi di test, ecco un breve elenco utilizzato per generare alcuni dati di esempio che volevo almeno verificare che il codice mapReduce funzionasse come dovrebbe:

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

Gli array sono valori di punto decimale singolo totalmente casuali, quindi non c'è molta distribuzione nei risultati elencati che ho fornito come output di esempio.