2015-05-17 19 views
6

Chcę obliczyć pole ratingu dla tego obiektu z polami ocen wewnątrz ocen tablicy. Czy możesz mi pomóc zrozumieć, jak używać agregacji z $ avg?Obliczanie średniej pól w dokumentach osadzonych/macierzy

{ 
    "title": "The Hobbit", 
    "rating_average": "???", 
    "ratings": [ 
     { 
      "title": "best book ever", 
      "rating": 5 
     }, 
     { 
      "title": "good book", 
      "rating": 3.5 
     } 
    ] 
} 

Odpowiedz

9

aggregation framework w MongoDB 3.4 i nowsze oferuje operatorowi $reduce który efektywnie oblicza całkowitą bez konieczności stosowania dodatkowych rurociągów. Rozważ użycie go jako wyrażenia, aby zwrócić całkowitą liczbę ocen i uzyskać liczbę ocen za pomocą $size. Wraz z $addFields średnia może zatem być obliczana za pomocą operatora arytmetyczną $divide jak we wzorze average = total ratings/number of ratings:

db.collection.aggregate([ 
    { 
     "$addFields": { 
      "rating_average": { 
       "$divide": [ 
        { // expression returns total 
         "$reduce": { 
          "input": "$ratings", 
          "initialValue": 0, 
          "in": { "$add": ["$$value", "$$this.rating"] } 
         } 
        }, 
        { // expression returns ratings count 
         "$cond": [ 
          { "$ne": [ { "$size": "$ratings" }, 0 ] }, 
          { "$size": "$ratings" }, 
          1 
         ] 
        } 
       ] 
      } 
     } 
    }   
]) 

próbek wyjściowych

{ 
    "_id" : ObjectId("58ab48556da32ab5198623f4"), 
    "title" : "The Hobbit", 
    "ratings" : [ 
     { 
      "title" : "best book ever", 
      "rating" : 5.0 
     }, 
     { 
      "title" : "good book", 
      "rating" : 3.5 
     } 
    ], 
    "rating_average" : 4.25 
} 

przypadku starszych wersjach musisz najpierw zastosować operatora $unwind na ratings najpierw pole tablicy jako początkowy krok agregacji potoków. Spowoduje to dekonstrukcję pola tablicy ratings z dokumentów wejściowych, aby wyprowadzić dokument dla każdego elementu. Każdy dokument wyjściowy zastępuje tablicę wartością elementu.

Drugi etap Rurociąg operator $group które dokumenty wprowadzania grupy przez _id i title kluczy identyfikatora ekspresji i stosuje się żądany ekspresji $avg akumulatora z każdej grupy oblicza się średnią. Istnieje inny operator akumulatora $push, który zachowuje pole oryginalnej tablicy ocen, zwracając tablicę wszystkich wartości, które wynikają z zastosowania wyrażenia do każdego dokumentu w powyższej grupie.

Ostatnim etapem procesu jest operator $project, który zmienia kształt każdego dokumentu w strumieniu, na przykład dodając nowe pole ratings_average.

Tak więc, jeśli na przykład masz przykładowy dokument w swojej kolekcji (jak z góry i tak poniżej):

db.collection.insert({ 
    "title": "The Hobbit", 

    "ratings": [ 
     { 
      "title": "best book ever", 
      "rating": 5 
     }, 
     { 
      "title": "good book", 
      "rating": 3.5 
     } 
    ] 
}) 

Aby obliczyć średnią ocen tablicy i wystająca wartość w innej dziedzinie ratings_average, można wówczas stosuje się następujące agregacji rurociągu:

db.collection.aggregate([ 
    { 
     "$unwind": "$ratings" 
    }, 
    { 
     "$group": { 
      "_id": { 
       "_id": "$_id", 
       "title": "$title" 
      }, 
      "ratings":{ 
       "$push": "$ratings" 
      }, 
      "ratings_average": { 
       "$avg": "$ratings.rating" 
      } 
     } 
    }, 
    { 
     "$project": { 
      "_id": 0, 
      "title": "$_id.title", 
      "ratings_average": 1, 
      "ratings": 1 
     } 
    } 
]) 

Wynik:

/* 1 */ 
{ 
    "result" : [ 
     { 
      "ratings" : [ 
       { 
        "title" : "best book ever", 
        "rating" : 5 
       }, 
       { 
        "title" : "good book", 
        "rating" : 3.5 
       } 
      ], 
      "ratings_average" : 4.25, 
      "title" : "The Hobbit" 
     } 
    ], 
    "ok" : 1 
} 
+1

Dziękuję, chridam :) – retrobitguy

+0

@retrobitguy Bez obaw :-) – chridam

+1

To było bardzo pomocne i jasne! Wielkie dzięki! – retrobitguy

2

Ponieważ masz w tablicy dane, które chcesz obliczyć na podstawie średniej, najpierw musisz je rozwinąć. Zrób to za pomocą $unwind w agregacji rurociągu:

{$unwind: "$ratings"} 

Następnie można uzyskać dostęp do każdego elementu tablicy jako dokument osadzony z kluczem ratings w dokumentach wyniku agregacji.Potem wystarczy $group przez title i obliczyć $avg:

{$group: {_id: "$title", ratings: {$push: "$ratings"}, average: {$avg: "$ratings.rating"}}} 

Następnie wystarczy odzyskać title murawę:

{$project: {_id: 0, title: "$_id", ratings: 1, average: 1}} 

Więc tutaj jest wynikiem agregacji rurociąg:

db.yourCollection.aggregate([ 
           {$unwind: "$ratings"}, 
           {$group: {_id: "$title", 
             ratings: {$push: "$ratings"}, 
             average: {$avg: "$ratings.rating"} 
             } 
           }, 
           {$project: {_id: 0, title: "$_id", ratings: 1, average: 1}} 
          ]) 
+1

Dziękuję n9code! – retrobitguy

3

To naprawdę można napisać o wiele krócej, a było tak nawet w chwili pisania. Jeśli chcesz „przeciętnego” wystarczy użyć $avg:

db.collection.aggregate([ 
    { "$addFields": { 
    "rating_average": { "$avg": "$ratings.rating" } 
    }} 
]) 

Powodem tego jest to, że od MongoDB 3.2 operator $avg zyskał „dwa” rzeczy:

  1. umiejętność przetworzyć " tablica "argumentów w formie" wyrażenia ", a nie wyłącznie jako akumulatora do korzyści z funkcji MongoDB 3.2, które zezwalały na" stenograficzną "notację wyrażeń tablicowych. Być albo w składzie:

    { "array": [ "$fielda", "$fieldb" ] } 
    

    lub w notating pojedynczy obiekt z tablicy jako tablica wartości tej właściwości:

    { "$avg": "$ratings.rating" } // equal to { "$avg": [ 5, 3.5 ] } 
    

We wcześniejszych wersjach trzeba by użyć $map w celu uzyskania dostępu do właściwości "rating" wewnątrz każdego elementu tablicy. Teraz nie.


Dla przypomnienia, nawet użycie $reduce można uprościć:

db.collection.aggregate([ 
    { "$addFields": { 
    "rating_average": { 
     "$reduce": { 
     "input": "$ratings", 
     "initialValue": 0, 
     "in": { 
      "$add": [ 
      "$$value", 
      { "$divide": [ 
       "$$this.rating", 
       { "$size": { "$ifNull": [ "$ratings", [] ] } } 
      ]} 
      ] 
     } 
     } 
    } 
    }} 
]) 

Tak jak wspomniano, jest to naprawdę tylko ponownego wdrożenia istniejących $avg funkcjonalność, a więc od tego operatora jest dostępny następnie to ten powinien być użyty.