Nuværende behandling er mapReduce
Hvis du har brug for at udføre dette på serveren og sortere topresultaterne og bare beholde top 100, så kan du bruge mapReduce til dette sådan:
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å kortlægningsfunktionen udfører beregningen og outputtet alt under den samme tast, så alle resultater sendes til reducereren. Slutoutputtet vil blive indeholdt i en matrix i et enkelt outputdokument, så det er både vigtigt, at alle resultater udsendes med den samme nøgleværdi, og at outputtet af hver udsendelse i sig selv er en matrix, så mapReduce kan fungere korrekt.
Sorteringen og reduktionen udføres i selve reduceringen, efterhånden som hvert udsendt dokument inspiceres, sættes elementerne i et enkelt midlertidigt array, sorteres, og de øverste resultater returneres.
Det er vigtigt, og netop grunden til, at emitteren producerer dette som et array, selvom et enkelt element i starten. MapReduce fungerer ved at behandle resultater i "bidder", så selvom alle udsendte dokumenter har den samme nøgle, bliver de ikke alle behandlet på én gang. Reduktionen sætter snarere sine resultater tilbage i køen af udsendte resultater for at blive reduceret, indtil der kun er et enkelt dokument tilbage for den pågældende nøgle.
Jeg begrænser "slice"-outputtet her til 10 for at gøre listen kortfattet, og inkluderer statistikken for at gøre en pointe, da de 100 reduktionscyklusser kaldet på denne 10.000 prøve 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å dette er et enkelt dokumentoutput, i det specifikke mapReduce-format, hvor "værdien" indeholder et element, som er en matrix af det sorterede og begrænsede resultat.
Fremtidig behandling er samlet
I skrivende stund er den aktuelle seneste stabile udgivelse af MongoDB 3.0, og denne mangler funktionaliteten til at gøre din drift mulig. Men den kommende 3.2-udgivelse introducerer nye operatører, der gør dette muligt:
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 }
])
Hvis du også begrænser til de samme 10 resultater for kortheds skyld, får du output som dette:
{ "_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 }
Dette er gjort muligt hovedsageligt på grund af $unwind
bliver ændret til at projicere et felt i resultater, der indeholder array-indekset, og også på grund af $arrayElemAt
som er en ny operator, der kan udtrække et array-element som en singular værdi fra et angivet indeks.
Dette tillader "opslag" af værdier efter indeksposition fra dit input-array for at anvende matematikken til hvert element. Input-arrayet er lettet af den eksisterende $literal
operator så $arrayElemAt
klager ikke og genkender det som et array, (synes at være en lille fejl i øjeblikket, da andre array-funktioner ikke har problemet med direkte input) og får den passende matchende indeksværdi ved at bruge "indeks"-feltet produceret af $unwind
til sammenligning.
Matematikken udføres af $subtract
og selvfølgelig endnu en ny operatør i $abs
for at opfylde din funktionalitet. Også da det var nødvendigt at afvikle arrayet i første omgang, sker alt dette inde i en $group
trin akkumulerer alle array-medlemmer pr. dokument og anvender tilføjelse af indgange via $sum
akkumulator.
Endelig behandles alle resultatdokumenter med $sort
og derefter $limit
anvendes til blot at returnere de bedste resultater.
Oversigt
Selv med den nye funktionalitet, der er ved at være tilgængelig for aggregeringsrammen for MongoDB, kan det diskuteres, hvilken tilgang der faktisk er mere effektiv for resultater. Dette skyldes i høj grad, at der stadig er et behov for at $unwind
array-indholdet, som effektivt producerer en kopi af hvert dokument pr. array-medlem i den pipeline, der skal behandles, og det forårsager generelt en overhead.
Så selvom mapReduce er den eneste nuværende måde at gøre dette på indtil en ny udgivelse, kan den faktisk udkonkurrere aggregeringserklæringen afhængigt af mængden af data, der skal behandles, og på trods af at aggregeringsrammen fungerer på native kodede operatører i stedet for oversat JavaScript operationer.
Som med alle andre ting, anbefales det altid at teste for at se, hvilken sag der passer bedst til dine formål, og som giver den bedste ydeevne til din forventede behandling.
Eksempel
Det forventede resultat for eksempeldokumentet i spørgsmålet er naturligvis 0.9
ved den anvendte matematik. Men kun til mit testformål er her en kort liste, der bruges til at generere nogle eksempeldata, som jeg i det mindste ønskede at bekræfte, at mapReduce-koden virkede, 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();
}
}
Arrays er totalt tilfældige enkeltdecimalværdier, så der er ikke megen distribution i de anførte resultater, jeg gav som eksempeloutput.