MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

MongoDB berechnet Werte aus zwei Arrays, Sort und Limit

Aktuelle Verarbeitung ist mapReduce

Wenn Sie dies auf dem Server ausführen und die Top-Ergebnisse sortieren und nur die Top 100 behalten müssen, können Sie mapReduce dafür wie folgt verwenden:

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

Die Mapper-Funktion führt also die Berechnung durch und gibt alles unter demselben Schlüssel aus, sodass alle Ergebnisse an den Reducer gesendet werden. Die Endausgabe wird in einem Array in einem einzigen Ausgabedokument enthalten sein, daher ist es sowohl wichtig, dass alle Ergebnisse mit demselben Schlüsselwert ausgegeben werden, als auch dass die Ausgabe jeder Ausgabe selbst ein Array ist, damit mapReduce ordnungsgemäß funktionieren kann /P>

Das Sortieren und Reduzieren erfolgt im Reduzierer selbst, da jedes ausgegebene Dokument überprüft wird, die Elemente in ein einzelnes temporäres Array gestellt, sortiert und die besten Ergebnisse zurückgegeben werden.

Das ist wichtig und genau der Grund, warum der Emitter dies als Array erzeugt, auch wenn es zunächst ein einzelnes Element ist. MapReduce verarbeitet die Ergebnisse in „Blöcken“. Selbst wenn alle ausgegebenen Dokumente denselben Schlüssel haben, werden sie nicht alle auf einmal verarbeitet. Vielmehr stellt der Reduzierer seine Ergebnisse zurück in die Warteschlange der ausgegebenen Ergebnisse, um sie zu reduzieren, bis nur noch ein einziges Dokument für diesen bestimmten Schlüssel übrig ist.

Ich beschränke die "Slice"-Ausgabe hier auf 10, um die Auflistung kurz zu halten, und füge die Statistiken hinzu, um einen Punkt zu machen, da die 100 Reduktionszyklen, die für diese 10000-Probe aufgerufen werden, zu sehen sind:

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

Dies ist also eine einzelne Dokumentausgabe im spezifischen mapReduce-Format, wobei der "Wert" ein Element enthält, das ein Array des sortierten und begrenzten Ergebnisses ist.

Zukünftige Verarbeitung ist aggregiert

Zum Zeitpunkt des Schreibens ist die aktuelle neueste stabile Version von MongoDB 3.0, und dieser fehlt die Funktionalität, um Ihren Betrieb zu ermöglichen. Aber die kommende Version 3.2 führt neue Operatoren ein, die dies ermöglichen:

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

Wenn Sie der Kürze halber ebenfalls auf die gleichen 10 Ergebnisse beschränken, erhalten Sie eine Ausgabe wie diese:

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

Dies wird hauptsächlich durch $unwind ermöglicht geändert, um ein Feld in Ergebnissen zu projizieren, das den Array-Index enthält, und auch aufgrund von $arrayElemAt Dies ist ein neuer Operator, der ein Array-Element als singulären Wert aus einem bereitgestellten Index extrahieren kann.

Dies ermöglicht das "Nachschlagen" von Werten nach Indexposition aus Ihrem Eingabearray, um die Mathematik auf jedes Element anzuwenden. Das Eingabearray wird durch den vorhandenen $literal Operator also $arrayElemAt beschwert sich nicht und erkennt es als ein Array ( scheint derzeit ein kleiner Fehler zu sein, da andere Array-Funktionen das Problem mit der direkten Eingabe nicht haben ) und erhält den entsprechenden passenden Indexwert, indem das von erzeugte "index" -Feld verwendet wird $unwind zum Vergleich.

Die Berechnung erfolgt durch $subtract und natürlich ein weiterer neuer Operator in $abs um Ihre Funktionalität zu erfüllen. Da es außerdem notwendig war, das Array überhaupt erst aufzuwickeln, wird all dies in einem $gruppe Phase, in der alle Array-Mitglieder pro Dokument gesammelt und das Hinzufügen von Einträgen über $summe Akku.

Abschließend werden alle Ergebnisdokumente mit $sort verarbeitet und dann $limit wird angewendet, um nur die besten Ergebnisse zurückzugeben.

Zusammenfassung

Selbst mit der neuen Funktionalität, die dem Aggregationsframework für MongoDB zur Verfügung stehen wird, ist es fraglich, welcher Ansatz tatsächlich effizienter für Ergebnisse ist. Dies liegt hauptsächlich daran, dass immer noch $unwind benötigt wird den Array-Inhalt, der effektiv eine Kopie jedes Dokuments pro Array-Mitglied in der zu verarbeitenden Pipeline erzeugt, und das verursacht im Allgemeinen einen Overhead.

Obwohl mapReduce bis zu einer neuen Version die einzige derzeitige Möglichkeit ist, dies zu tun, kann es die Aggregationsanweisung abhängig von der zu verarbeitenden Datenmenge tatsächlich übertreffen, und das trotz der Tatsache, dass das Aggregationsframework mit nativen codierten Operatoren und nicht mit übersetztem JavaScript arbeitet Operationen.

Wie bei allen Dingen wird immer ein Test empfohlen, um zu sehen, welches Gehäuse besser zu Ihren Zwecken passt und welches die beste Leistung für Ihre erwartete Verarbeitung bietet.

Beispiel

Natürlich ist das erwartete Ergebnis für das in der Frage angegebene Beispieldokument 0.9 durch die angewandte Mathematik. Aber nur für meine Testzwecke ist hier eine kurze Liste, die verwendet wurde, um einige Beispieldaten zu generieren, die ich zumindest überprüfen wollte, ob der mapReduce-Code so funktioniert, wie er sollte:

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

Die Arrays sind völlig zufällige einzelne Dezimalpunktwerte, daher gibt es keine große Verteilung in den aufgelisteten Ergebnissen, die ich als Beispielausgabe gegeben habe.