sql >> Database >  >> NoSQL >> MongoDB

MongoDB Bereken waarden uit twee arrays, sorteren en beperken

Huidige verwerking is mapReduce

Als u dit op de server moet uitvoeren en de topresultaten moet sorteren en alleen de top 100 moet behouden, dan kunt u hiervoor mapReduce als volgt gebruiken:

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

Dus de mapper-functie doet de berekening en voert alles uit onder dezelfde sleutel, zodat alle resultaten naar het verloopstuk worden verzonden. De einduitvoer zal worden opgenomen in een array in een enkel uitvoerdocument, dus het is zowel belangrijk dat alle resultaten worden uitgezonden met dezelfde sleutelwaarde als dat de uitvoer van elke uitzending zelf een array is, zodat mapReduce goed kan werken.

Het sorteren en verkleinen gebeurt in het verloopstuk zelf, terwijl elk verzonden document wordt geïnspecteerd, de elementen in een enkele tijdelijke array worden geplaatst, gesorteerd en de topresultaten worden geretourneerd.

Dat is belangrijk, en precies de reden waarom de emitter dit als een array produceert, zelfs als een enkel element in het begin. MapReduce werkt door resultaten in "chunks" te verwerken, dus zelfs als alle verzonden documenten dezelfde sleutel hebben, worden ze niet allemaal tegelijk verwerkt. In plaats daarvan plaatst de verkleiner zijn resultaten terug in de rij met verzonden resultaten om te worden verkleind totdat er nog maar één document over is voor die specifieke sleutel.

Ik beperk de "slice" -uitvoer hier tot 10 voor de beknoptheid van de lijst, en inclusief de statistieken om een ​​punt te maken, aangezien de 100 reductiecycli die op dit 10000-monster worden aangeroepen, te zien zijn:

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

Dit is dus een uitvoer van één document, in het specifieke mapReduce-formaat, waarbij de "waarde" een element bevat dat een array is van het gesorteerde en beperkte resultaat.

Toekomstige verwerking is geaggregeerd

Op het moment van schrijven is de huidige nieuwste stabiele release van MongoDB 3.0, en dit mist de functionaliteit om uw operatie mogelijk te maken. Maar de aankomende 3.2-release introduceert nieuwe operators die dit mogelijk maken:

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

Beperkt u zich ook tot dezelfde 10 resultaten voor de beknoptheid, u krijgt de volgende uitvoer:

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

Dit wordt grotendeels mogelijk gemaakt door $unwind gewijzigd om een ​​veld in resultaten te projecteren dat de array-index bevat, en ook vanwege $arrayElemAt dat is een nieuwe operator die een array-element als een enkelvoudige waarde uit een opgegeven index kan extraheren.

Dit maakt het "opzoeken" van waarden op indexpositie van uw invoerarray mogelijk om de wiskunde op elk element toe te passen. De invoerarray wordt vergemakkelijkt door de bestaande $literal operator dus $arrayElemAt klaagt niet en herkent het als een array (lijkt op dit moment een kleine bug te zijn, omdat andere arrayfuncties het probleem niet hebben met directe invoer) en krijgt de juiste overeenkomende indexwaarde door het "index" -veld te gebruiken dat is geproduceerd door $unwind ter vergelijking.

De berekening wordt gedaan door $subtract en natuurlijk nog een nieuwe operator in $abs om aan uw functionaliteit te voldoen. Omdat het in de eerste plaats nodig was om de array af te wikkelen, wordt dit allemaal gedaan in een $group fase waarin alle arrayleden per document worden verzameld en de toevoeging van items wordt toegepast via de $sum accumulator.

Uiteindelijk worden alle resultaatdocumenten verwerkt met $sort en dan de $limit wordt toegepast om alleen de beste resultaten te retourneren.

Samenvatting

Zelfs met de nieuwe functionaliteit die binnenkort beschikbaar komt voor het aggregatieraamwerk voor MongoDB, is het de vraag welke aanpak eigenlijk efficiënter is voor resultaten. Dit is grotendeels te wijten aan het feit dat er nog steeds behoefte is aan $unwind de array-inhoud, die in feite een kopie van elk document per arraylid in de te verwerken pijplijn produceert, en dat veroorzaakt over het algemeen overhead.

Dus hoewel mapReduce de enige huidige manier is om dit te doen tot een nieuwe release, kan het zelfs beter presteren dan de aggregatieverklaring, afhankelijk van de hoeveelheid gegevens die moet worden verwerkt, en ondanks het feit dat het aggregatieraamwerk werkt op native gecodeerde operators in plaats van vertaald JavaScript operaties.

Zoals met alle dingen, wordt testen altijd aanbevolen om te zien welke case het beste bij uw doeleinden past en welke de beste prestaties levert voor uw verwachte verwerking.

Voorbeeld

Natuurlijk is het verwachte resultaat voor het voorbeelddocument in de vraag 0.9 door de toegepaste wiskunde. Maar alleen voor mijn testdoeleinden, hier is een korte lijst die is gebruikt om enkele voorbeeldgegevens te genereren waarvan ik op zijn minst wilde verifiëren dat de mapReduce-code naar behoren werkte:

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

De arrays zijn volledig willekeurige waarden met één decimale punt, dus er is niet veel distributie in de weergegeven resultaten die ik als voorbeelduitvoer heb gegeven.




  1. hoe mongoDB-object-id in java te maken

  2. Meerdere subdocumenten bijwerken zonder exacte voorwaarde op subdocumentniveau

  3. MongoDB-query in Spring-repository:beperk het aantal records na filter

  4. Wanneer CouchDB over MongoDB gebruiken en vice versa?