Текущата обработка е 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();
}
}
Масивите са напълно произволни стойности с една десетична запетая, така че няма много разпределение в изброените резултати, които дадох като примерен резултат.