sql >> Databasteknik >  >> NoSQL >> MongoDB

MongoDB Beräkna värden från två matriser, sortera och begränsa

Nuvarande bearbetning är mapReduce

Om du behöver köra detta på servern och sortera toppresultaten och bara behålla topp 100, kan du använda mapReduce för detta så här:

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

Så mapparfunktionen gör beräkningen och utmatningen av allt under samma tangent så att alla resultat skickas till reduceraren. Slututgången kommer att finnas i en array i ett enda utgångsdokument, så det är både viktigt att alla resultat sänds ut med samma nyckelvärde och att utgången från varje emit i sig är en array så att mapReduce kan fungera korrekt.

Sorteringen och minskningen görs i själva reduceringen, när varje utsänt dokument inspekteras placeras elementen i en enda tillfällig array, sorteras och de bästa resultaten returneras.

Det är viktigt, och bara anledningen till att sändaren producerar detta som en array även om ett enda element först. MapReduce fungerar genom att bearbeta resultat i "bitar", så även om alla utsända dokument har samma nyckel, bearbetas inte alla samtidigt. Snarare lägger reduceraren tillbaka sina resultat i kön av utsända resultat för att reduceras tills det bara finns ett enda dokument kvar för just den nyckeln.

Jag begränsar "slice"-utmatningen här till 10 för kortfattad lista, och inkluderar statistiken för att göra en poäng, eftersom de 100 reduceringscyklerna som anropas på detta 10000-prov kan ses:

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

Så detta är en enda dokumentutmatning, i det specifika mapReduce-formatet, där "värdet" innehåller ett element som är en array av det sorterade och begränsade resultatet.

Framtida bearbetning är aggregerad

När det skrivs är den nuvarande senaste stabila versionen av MongoDB 3.0, och detta saknar funktionaliteten för att göra din operation möjlig. Men den kommande 3.2-versionen introducerar nya operatörer som gör detta möjligt:

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

Om du också begränsar dig till samma 10 resultat för korthetens skull får du utdata så här:

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

Detta är möjligt till stor del tack vare $unwind modifieras för att projicera ett fält i resultat som innehåller arrayindexet, och även på grund av $arrayElemAt som är en ny operator som kan extrahera ett arrayelement som ett singulärvärde från ett tillhandahållet index.

Detta gör det möjligt att "slå upp" värden efter indexposition från din inmatningsmatris för att tillämpa matematiken på varje element. Inmatningsmatrisen underlättas av den befintliga $literal operator så $arrayElemAt klagar inte och känner igen det som en array ( verkar vara en liten bugg för närvarande, eftersom andra arrayfunktioner inte har problemet med direktinmatning ) och får lämpligt matchande indexvärde genom att använda "index"-fältet som produceras av $unwind för jämförelse.

Matematiken görs av $subtract och naturligtvis ytterligare en ny operatör i $abs för att möta din funktionalitet. Eftersom det var nödvändigt att varva ner arrayen i första hand, görs allt detta i en $group steg ackumulerar alla arraymedlemmar per dokument och tillämpar tillägg av poster via $sum ackumulator.

Slutligen bearbetas alla resultatdokument med $sort och sedan $limit används för att bara returnera de bästa resultaten.

Sammanfattning

Även med den nya funktionaliteten på väg att vara tillgänglig för aggregeringsramverket för MongoDB kan det diskuteras vilket tillvägagångssätt som faktiskt är mer effektivt för resultat. Detta beror till stor del på att det fortfarande finns ett behov av att $unwind arrayinnehållet, som effektivt producerar en kopia av varje dokument per arraymedlem i den pipeline som ska bearbetas, och som vanligtvis orsakar en overhead.

Så även om mapReduce är det enda nuvarande sättet att göra detta fram till en ny release, kan det faktiskt överträffa aggregeringssatsen beroende på mängden data som ska bearbetas, och trots att aggregeringsramverket fungerar på inbyggda kodade operatörer snarare än översatt JavaScript. operationer.

Som med alla saker rekommenderas alltid testning för att se vilket fall som passar dina syften bättre och vilket ger bäst prestanda för din förväntade bearbetning.

Exempel

Naturligtvis är det förväntade resultatet för exempeldokumentet som tillhandahålls i frågan 0.9 av den tillämpade matematiken. Men bara för mina testsyften, här är en kort lista som används för att generera några exempeldata som jag åtminstone ville verifiera att mapReduce-koden fungerade som den skulle:

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

Matriserna är helt slumpmässiga enstaka decimaler, så det finns inte mycket distribution i de listade resultaten jag gav som exempelutdata.




  1. phpredis på fedora 12

  2. Vad är snabbare:`find().limit(1)` eller `findOne()` i MongoDB/Mongoose?

  3. Hur man ansluter till Redis-instans (memorystore) från Googles standardappmotor (Python 3.7)

  4. mongodb:fråga för tidsperioden mellan två datumfält