Bieżące przetwarzanie to mapReduce
Jeśli musisz wykonać to na serwerze i posortować najlepsze wyniki, a po prostu zachować pierwszą setkę, możesz użyć mapReduce w tym celu:
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 } }
)
Tak więc funkcja mapowania wykonuje obliczenia i wyprowadza wszystko pod tym samym kluczem, więc wszystkie wyniki są wysyłane do reduktora. Wynik końcowy będzie zawarty w tablicy w pojedynczym dokumencie wyjściowym, dlatego ważne jest, aby wszystkie wyniki były emitowane z tą samą wartością klucza, a dane wyjściowe każdego emitowania były same w sobie tablicą, aby funkcja mapReduce mogła działać poprawnie.
Sortowanie i redukowanie odbywa się w samym reduktorze, ponieważ każdy wyemitowany dokument jest sprawdzany, elementy są umieszczane w pojedynczej tablicy tymczasowej, sortowane i zwracane są najlepsze wyniki.
To jest ważne i jest to tylko powód, dla którego emiter tworzy to jako tablicę, nawet jeśli początkowo jest to pojedynczy element. MapReduce działa, przetwarzając wyniki w „kawałkach”, więc nawet jeśli wszystkie emitowane dokumenty mają ten sam klucz, nie wszystkie są przetwarzane jednocześnie. Zamiast tego reduktor umieszcza wyniki z powrotem w kolejce wyemitowanych wyników w celu zmniejszenia, dopóki nie pozostanie tylko jeden dokument dla tego konkretnego klucza.
Ograniczam tutaj wyjście „plasterka” do 10, aby uzyskać zwięzłość zestawienia, a także statystyki, aby wskazać punkt, ponieważ można zobaczyć 100 cykli redukcji wywołanych w tej próbce 10000:
{
"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
}
Jest to więc pojedynczy dokument wyjściowy, w specyficznym formacie mapReduce, w którym „wartość” zawiera element będący tablicą posortowanego i ograniczonego wyniku.
Przyszłe przetwarzanie jest zagregowane
W chwili pisania tego tekstu, najnowsza stabilna wersja MongoDB to 3.0, a to nie ma funkcji umożliwiającej działanie. Ale nadchodzące wydanie 3.2 wprowadza nowe operatory, które to umożliwiają:
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 }
])
Ograniczając się do tych samych 10 wyników dla zwięzłości, otrzymujesz dane wyjściowe w następujący sposób:
{ "_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 }
Jest to możliwe głównie dzięki $unwind
zmodyfikowane w celu wyświetlenia pola w wynikach, które zawiera indeks tablicy, a także z powodu $arrayElemAt
który jest nowym operatorem, który może wyodrębnić element tablicy jako wartość pojedynczą z podanego indeksu.
Pozwala to na "wyszukiwanie" wartości według pozycji indeksu z tablicy wejściowej w celu zastosowania matematyki do każdego elementu. Tablica wejściowa jest obsługiwana przez istniejący $literal
operator więc $arrayElemAt
nie narzeka i rekonguje go jako tablicę (wydaje się, że jest to obecnie mały błąd, ponieważ inne funkcje tablicowe nie mają problemu z bezpośrednim wejściem) i uzyskuje odpowiednią wartość indeksu dopasowania za pomocą pola „indeks” utworzonego przez $unwind
dla porównania.
Matematyka jest wykonywana przez $subtract
i oczywiście inny nowy operator w $abs
aby spełnić Twoją funkcjonalność. Ponieważ konieczne było rozwinięcie tablicy, wszystko to odbywa się w $group
etap akumulacji wszystkich członków tablicy dla każdego dokumentu i zastosowanie dodawania wpisów za pomocą $sum
akumulator.
Na koniec wszystkie dokumenty wynikowe są przetwarzane za pomocą $sort
a następnie $limit
jest stosowany tylko do zwrócenia najlepszych wyników.
Podsumowanie
Nawet z nową funkcjonalnością, która ma być dostępna w ramach agregacji dla MongoDB, jest dyskusyjne, które podejście jest faktycznie bardziej efektywne dla wyników. Wynika to w dużej mierze z tego, że nadal istnieje potrzeba $unwind
zawartość tablicy, która efektywnie tworzy kopię każdego dokumentu na element składowy tablicy w potoku do przetworzenia, a to generalnie powoduje obciążenie.
Tak więc, chociaż mapReduce jest jedynym obecnym sposobem na zrobienie tego aż do nowej wersji, może faktycznie przewyższyć instrukcję agregacji w zależności od ilości danych do przetworzenia i pomimo faktu, że struktura agregacji działa na natywnych operatorach kodowanych, a nie na przetłumaczonym JavaScript operacje.
Podobnie jak w przypadku wszystkich innych rzeczy, zawsze zaleca się testowanie, aby sprawdzić, który przypadek lepiej odpowiada Twoim celom i który zapewnia najlepszą wydajność dla oczekiwanego przetwarzania.
Próbka
Oczywiście oczekiwany wynik dla przykładowego dokumentu podanego w pytaniu to 0.9
przez zastosowaną matematykę. Ale tylko dla moich celów testowych, oto krótka lista używana do generowania niektórych przykładowych danych, które chciałem przynajmniej zweryfikować, czy kod mapReduce działa tak, jak powinien:
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();
}
}
Tablice są całkowicie losowymi wartościami z pojedynczym przecinkiem dziesiętnym, więc nie ma dużego rozkładu w wymienionych wynikach, które podałem jako przykładowe wyjście.