Wygląda na to, że już zacząłeś, ale zgubiłeś się w niektórych innych koncepcjach. Istnieje kilka podstawowych prawd dotyczących pracy z tablicami w dokumentach, ale zacznijmy od miejsca, w którym zostało przerwane:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 }
}}
])
Będzie to po prostu użycie $group
potok do zebrania dokumentów na podstawie różnych wartości pola "status", a następnie wytworzenia innego pola dla "count", które oczywiście "zlicza" wystąpienia klucza grupującego, przekazując wartość 1
do $sum
operatora dla każdego znalezionego dokumentu. To stawia Cię w miejscu, które opisujesz:
{ "_id" : "done", "count" : 2 }
{ "_id" : "canceled", "count" : 1 }
To pierwszy etap tego wszystkiego i dość łatwy do zrozumienia, ale teraz musisz wiedzieć, jak uzyskać wartości z tablicy. Możesz ulec pokusie, gdy zrozumiesz "notację z kropkami" koncept prawidłowo, aby zrobić coś takiego:
db.sample.aggregate([
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
Ale okaże się, że „suma” w rzeczywistości będzie wynosić 0
dla każdego z tych wyników:
{ "_id" : "done", "count" : 2, "total" : 0 }
{ "_id" : "canceled", "count" : 1, "total" : 0 }
Czemu? Cóż, operacje agregacji MongoDB, takie jak ta, w rzeczywistości nie przechodzą przez elementy tablicy podczas grupowania. W tym celu w ramach agregacji zastosowano koncepcję o nazwie $unwind
. Nazwa jest stosunkowo oczywista. Wbudowana tablica w MongoDB przypomina powiązanie „jeden do wielu” między połączonymi źródłami danych. Co z tego $unwind
robi to dokładnie ten rodzaj wyniku „dołączenia”, w którym wynikowe „dokumenty” są oparte na zawartości tablicy i zduplikowanych informacjach dla każdego rodzica.
Aby więc działać na elementach tablicy, musisz użyć $unwind
pierwszy. Powinno to logicznie prowadzić do takiego kodu:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$devices.cost" }
}}
])
A potem wynik:
{ "_id" : "done", "count" : 4, "total" : 700 }
{ "_id" : "canceled", "count" : 2, "total" : 350 }
Ale to nie do końca prawda? Pamiętaj, czego właśnie nauczyłeś się od $unwind
i jak zdenormalizowane połączenie z informacją rodzica? Więc teraz jest to duplikowane dla każdego dokumentu, ponieważ oba miały dwa elementy tablicy. Tak więc, podczas gdy pole „ogółem” jest poprawne, „liczba” jest w każdym przypadku dwa razy większa niż powinna.
Należy zachować nieco większą ostrożność, więc zamiast robić to w jednym $group
etap, odbywa się to w dwóch:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}}
])
Który teraz otrzymuje wynik z poprawnymi sumami w nim:
{ "_id" : "canceled", "count" : 1, "total" : 350 }
{ "_id" : "done", "count" : 2, "total" : 700 }
Teraz liczby się zgadzają, ale nadal nie jest to dokładnie to, o co prosisz. Myślę, że powinieneś na tym poprzestać, ponieważ rodzaj wyniku, którego oczekujesz, tak naprawdę nie pasuje do pojedynczego wyniku z samej agregacji. Szukasz sumy, która będzie „w środku” wyniku. To naprawdę nie pasuje, ale na małych danych jest w porządku:
db.sample.aggregate([
{ "$unwind": "$devices" },
{ "$group": {
"_id": "$_id",
"status": { "$first": "$status" },
"total": { "$sum": "$devices.cost" }
}},
{ "$group": {
"_id": "$status",
"count": { "$sum": 1 },
"total": { "$sum": "$total" }
}},
{ "$group": {
"_id": null,
"data": { "$push": { "count": "$count", "total": "$total" } },
"totalCost": { "$sum": "$total" }
}}
])
I ostateczny formularz wyników:
{
"_id" : null,
"data" : [
{
"count" : 1,
"total" : 350
},
{
"count" : 2,
"total" : 700
}
],
"totalCost" : 1050
}
Ale „Nie rób tego” . MongoDB ma limit dokumentów na odpowiedź 16MB, co jest ograniczeniem specyfikacji BSON. Na małych wynikach możesz zrobić tego rodzaju wygodne zawijanie, ale w większym schemacie rzeczy chcesz uzyskać wyniki we wcześniejszej formie i albo oddzielne zapytanie, albo żyć z iteracją całych wyników w celu uzyskania sumy ze wszystkich dokumentów.
Wygląda na to, że używasz MongoDB w wersji mniejszej niż 2.6 lub kopiujesz dane wyjściowe z powłoki RoboMongo, która nie obsługuje funkcji najnowszej wersji. Od MongoDB 2.6 wyniki agregacji mogą być „kursorem”, a nie pojedynczą tablicą BSON. Tak więc całkowita odpowiedź może być znacznie większa niż 16 MB, ale tylko wtedy, gdy nie kompaktujesz do pojedynczego dokumentu jako wyników, jak pokazano w ostatnim przykładzie.
Byłoby to szczególnie prawdziwe w przypadkach, w których „stronicowałeś” wyniki, z setkami do 1000 wierszy wyników, ale chciałeś po prostu zwrócić „suma” w odpowiedzi API, gdy zwracasz tylko „stronę” z 25 wynikami w raz.
W każdym razie powinno to dać ci rozsądny przewodnik, jak uzyskać typ wyników, których oczekujesz od wspólnego formularza dokumentu. Zapamiętaj $unwind
w celu przetwarzania tablic i ogólnie $group
wiele razy, aby uzyskać sumy na różnych poziomach grupowania z grupowania dokumentów i kolekcji.