MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Grupuj i licz za pomocą frameworka agregacji

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.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Jak używać operatora księgowego MongoDB w kodzie C#?

  2. Mongodb Explain for Aggregation Framework

  3. mongoDB - średnia z wartości tablicy

  4. Pętla z asynchronicznymi wywołaniami zwrotnymi w mongoose/mongodb/node

  5. Jak uniknąć dwóch jednoczesnych żądań API łamiących logikę walidacji dokumentów?