MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

MongoDB изчислява стойности от два масива, сортиране и ограничаване

Текущата обработка е mapReduce

Ако трябва да изпълните това на сървъра и да сортирате най-добрите резултати и просто да запазите първите 100, можете да използвате mapReduce за това по следния начин:

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

Така че функцията за картографиране прави изчислението и извежда всичко под един и същ ключ, така че всички резултати се изпращат към редуктора. Крайният изход ще се съдържа в масив в един изходен документ, така че е важно както всички резултати да се излъчват с една и съща стойност на ключ, така и изходът на всеки емит сам по себе си да е масив, така че mapReduce да може да работи правилно.

Сортирането и редуцирането се извършва в самия редуктор, тъй като всеки издаден документ се проверява, елементите се поставят в единичен временен масив, сортират се и се връщат най-добрите резултати.

Това е важно и само причината, поради която емитерът произвежда това като масив, дори ако първоначално е един елемент. MapReduce работи, като обработва резултатите на "късове", така че дори ако всички изпратени документи имат един и същ ключ, те не се обработват наведнъж. По-скоро редукторът връща резултатите си обратно в опашката с излъчени резултати, за да бъдат намалени, докато остане само един документ за този конкретен ключ.

Ограничавам изхода „slice“ тук до 10 за краткост на списъка и включвам статистиката, за да подчертая, тъй като 100-те редуциращи цикъла, извикани на тази 10 000 извадка, могат да се видят:

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

Така че това е един изходен документ в специфичния формат mapReduce, където „стойността“ съдържа елемент, който е масив от сортирания и ограничен резултат.

Бъдещата обработка е обобщена

Към момента на писане текущата най-нова стабилна версия на MongoDB е 3.0 и липсва функционалността, която да направи вашата операция възможна. Но предстоящата версия 3.2 въвежда нови оператори, които правят това възможно:

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

Освен това ограничавайки до същите 10 резултата за краткост, получавате резултат като този:

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

Това е възможно до голяма степен благодарение на $unwind се променя, за да проектира поле в резултатите, което съдържа индекса на масива, а също и поради $arrayElemAt което е нов оператор, който може да извлече елемент от масив като единствена стойност от предоставен индекс.

Това позволява "търсене" на стойности по позиция на индекс от вашия входен масив, за да се приложи математиката към всеки елемент. Входният масив се улеснява от съществуващия $literal оператор така $arrayElemAt не се оплаква и го разпознава като масив (изглежда, че това е малък бъг в момента, тъй като други функции на масива нямат проблем с директно въвеждане) и получава подходящата съответстваща стойност на индекса, като използва полето "индекс", произведено от $unwind за сравнение.

Математиката се извършва от $subtract и разбира се още един нов оператор в $abs за да отговаря на вашата функционалност. Освен това, тъй като беше необходимо да се развие масивът на първо място, всичко това се прави в $group етап на натрупване на всички членове на масива на документ и прилагане на добавянето на записи чрез $sum акумулатор.

Накрая всички документи с резултати се обработват с $sort и след това $limit се прилага само за връщане на най-добрите резултати.

Резюме

Дори с новата функционалност, която предстои да бъде достъпна за рамката за агрегиране за MongoDB, спорно е кой подход всъщност е по-ефективен за резултати. Това до голяма степен се дължи на това, че все още има нужда от $unwind съдържанието на масива, което ефективно създава копие на всеки документ за член на масива в конвейера, който трябва да бъде обработен, и това обикновено причинява излишък.

Така че, докато mapReduce е единственият настоящ начин да направите това до нова версия, той може действително да надмине отчета за агрегиране в зависимост от количеството данни, които трябва да бъдат обработени, и въпреки факта, че рамката за агрегиране работи с естествено кодирани оператори, а не с преведен JavaScript операции.

Както при всички неща, тестването винаги се препоръчва, за да видите кой случай отговаря по-добре на вашите цели и кой дава най-добра производителност за очакваната от вас обработка.

Пример

Разбира се, очакваният резултат за примерния документ, предоставен във въпроса, е 0.9 по приложената математика. Но само за моите тестови цели, ето кратък списък, използван за генериране на някои примерни данни, които исках поне да проверя дали кодът на mapReduce работи както трябва:

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

Масивите са напълно произволни стойности с една десетична запетая, така че няма много разпределение в изброените резултати, които дадох като примерен резултат.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Какъв е правилният модел за вложени схеми в Mongoose/MongoDB?

  2. Как мога да се свържа с mongodb с помощта на express без mongoose?

  3. MongoDB използва ли повторно изтрито пространство?

  4. Не може да се свърже Mongo shell с Mongo Atlas M0 чрез mongodb+srv

  5. Изберете въз основа на клеймото за време и актуализирайте клеймото с нула