2015-05-17 14 views
6

Ich möchte das rating_average-Feld dieses Objekts mit den Bewertungsfeldern innerhalb der Array-Bewertungen berechnen. Können Sie mir helfen zu verstehen, wie man die Aggregation mit $ avg verwendet? Berechne den Durchschnitt von Feldern in eingebetteten Dokumenten/Array

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

Antwort

9

Die aggregation framework in MongoDB 3.4 und höher bietet die $reduce Betreiber, die effizient die Summe für zusätzliche Pipelines ohne die Notwendigkeit berechnet. Erwägen Sie, es als Ausdruck zu verwenden, um die Gesamtbewertungen zurückzugeben und die Anzahl der Bewertungen unter Verwendung von $size zu erhalten. Zusammen mit $addFields kann die durchschnittliche damit den arithmetischen Operator $divide wie in der Formel average = total ratings/number of ratings berechnet werden:

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 
         ] 
        } 
       ] 
      } 
     } 
    }   
]) 

Beispielausgabe

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

Bei älteren Versionen Sie müssen zuerst den Operator $unwind auf der Anwendung anwenden ratings Array Feld zuerst als Ihre erste Aggregation Pipeline-Schritt. Dies dekonstruiert das Arrayfeld ratings aus den Eingabedokumenten, um ein Dokument für jedes Element auszugeben. Jedes Ausgabedokument ersetzt das Array durch einen Elementwert.

Die zweite Pipeline-Stufe würde die $group Operator die Dokumente Gruppen Eingabe durch den _id und title Ausdruck Schlüssel-Kennung und jeder Gruppe die gewünschte $avg Akkumulator Ausdruck gilt, der den Mittelwert berechnet. Es gibt einen weiteren Akkumulatoroperator, $push, der das Arrayfeld der ursprünglichen Bewertungen beibehält, indem ein Array aller Werte zurückgegeben wird, die sich aus der Anwendung eines Ausdrucks auf jedes Dokument in der obigen Gruppe ergeben. Der letzte Pipeline-Schritt ist der $project-Operator, der dann jedes Dokument im Stream umformt, z. B. durch Hinzufügen des neuen Felds ratings_average.

Also, wenn Sie zum Beispiel ein Beispieldokument in Ihrer Sammlung (von oben und so weiter unten):

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

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

Um die Bewertungen Array Durchschnitt zu berechnen und den Wert in einem anderen Feld Projizieren ratings_average, können Sie dann gilt die folgende Aggregations Pipeline:

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 
     } 
    } 
]) 

Ergebnis:

/* 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

Danke, chridam :) – retrobitguy

+0

@retrobitguy Keine Sorgen :-) – chridam

+1

Das war sehr hilfreich und klar! Danke vielmals! – retrobitguy

2

Da Sie Ihre Durchschnittsdaten in einem Array berechnen müssen, müssen Sie sie zuerst auflösen. Tun Sie es, indem Sie die $unwind in Ihrer Aggregation-Pipeline:

{$unwind: "$ratings"} 

Dann greifen Sie können jedes Element des Arrays als eingebettete Dokument mit den wichtigsten ratings in den Ergebnisdokumenten der Aggregation.Dann brauchen Sie nur zu $group von title und berechnen $avg:

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

Dann einfach Ihre title Feld recover:

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

So, hier ist Ihr Ergebnis Aggregation Pipeline:

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

Danke n9code! – retrobitguy

3

Das ist wirklich könnte so viel kürzer geschrieben werden, und das war sogar wahr zu der Zeit des Schreibens. Wenn Sie eine „durchschnittliche“ wollen einfach $avg verwenden:

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

Der Grund dafür ist, dass, wie von MongoDB 3.2 der $avg Operator „zwei“ Dinge gewonnen:

  1. Die Fähigkeit, ein "zu verarbeiten Array "von Argumenten in einer" Ausdruck "Form statt nur als Akkumulator zu $group

  2. Vorteile von den Funktionen von MongoDB 3.2, die die" shorthand "Notation von Array-Ausdrücke erlaubt. entweder zu sein in Zusammensetzung:

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

    oder in eine einzelne Eigenschaft aus dem Array als ein Array der Werte dieser Eigenschaft notating:

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

In früheren Versionen würden Sie haben $map verwenden um auf die Eigenschaft "rating" innerhalb jedes Array-Elements zuzugreifen. Jetzt tust du es nicht.


Für die Aufzeichnung auch die $reduce Nutzung vereinfacht werden kann:

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

Ja, das ist wirklich neu implementiert, ist nur die bestehende $avg Funktionalität und deshalb seit diesem Betreiber ist dann verfügbar es ist derjenige, der verwendet werden sollte.

Verwandte Themen