2015-05-17 26 views
6

Voglio calcolare il campo rating_average di questo oggetto con i campi di classificazione all'interno delle classificazioni dell'array. Puoi aiutarmi a capire come utilizzare l'aggregazione con $ avg?Calcola la media dei campi nei documenti/array incorporati

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

risposta

9

Il aggregation framework in MongoDB 3.4 e successivi offre all'operatore $reduce che calcola in modo efficiente il totale senza la necessità di tubazioni in più. Considera di usarlo come espressione per restituire il punteggio totale e ottenere il numero di valutazioni usando $size. Insieme a $addFields, la media può quindi essere calcolato utilizzando l'operatore aritmetico $divide come nella formula 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 
         ] 
        } 
       ] 
      } 
     } 
    }   
]) 

Esempio di output

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

Con le versioni precedenti, sarà necessario prima applicare l'operatore $unwind sul ratings campo array prima come passaggio iniziale della pipeline di aggregazione. Questo decostruirà il campo dell'array ratings dai documenti di input per produrre un documento per ciascun elemento. Ogni documento di output sostituisce l'array con un valore di elemento.

Il secondo stadio della pipeline sarebbe l'operatore $group quali documenti gruppi di ingresso dall'espressione identificatore _id e title chiavi e applica l'espressione $avg accumulatore desiderato per ogni gruppo che calcola la media. Esiste un altro operatore di accumulatori $push che conserva il campo dell'array di classificazione originale restituendo un array di tutti i valori che risultano dall'applicazione di un'espressione a ciascun documento nel gruppo precedente.

Il passaggio della pipeline finale è l'operatore $project che quindi rimodella ogni documento nello stream, ad esempio aggiungendo il nuovo campo ratings_average.

Quindi, se per esempio si dispone di un documento di esempio nella vostra collezione (come dall'alto e così di seguito):

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

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

per calcolare la media feedback array e proiettando il valore in un altro campo ratings_average, è possibile quindi applicare la seguente gasdotto di aggregazione:

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

Risultato:

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

Grazie, chridam :) – retrobitguy

+0

@retrobitguy Nessun problema :-) – chridam

+1

Questo è stato molto utile e chiaro! Grazie molto! – retrobitguy

2

Dato che si dispone di dati medi da calcolare in una matrice, è necessario innanzitutto svolgerlo. Fatelo utilizzando il $unwind nella vostra pipeline di aggregazione:

{$unwind: "$ratings"} 

allora si può accedere a ciascun elemento dell'array come un documento incorporato con chiave ratings nei documenti risultato dell'aggregazione.Allora non vi resta che $group da title e calcolare $avg:

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

Poi basta ripristinare il title campo:

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

ecco il risultato dell'aggregazione gasdotto:

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

Grazie n9code! – retrobitguy

3

Questo in realtà potrebbe essere scritto molto più breve, e questo era vero anche al momento della scrittura. Se si desidera un "media" semplicemente utilizzare $avg:

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

La ragione di questo è che a partire dal MongoDB 3.2 dell'operatore $avg guadagnato "due" cose:

  1. la capacità di elaborare una " matrice" di argomenti in un 'espressione' forma piuttosto che esclusivamente come accumulatore di $group

  2. vantaggi dalle caratteristiche di MongoDB 3.2 che hanno permesso la 'notazione abbreviata' di espressioni di matrice. Essere sia nella composizione:

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

    o in notating una singola proprietà dalla matrice come una matrice dei valori di quella proprietà:

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

Nelle versioni precedenti si dovrà utilizzare $map per accedere alla proprietà "rating" all'interno di ciascun elemento dell'array. Ora non lo fai.


Per la cronaca, anche l'utilizzo $reduce può essere semplificata:

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

Sì come detto, questo è davvero solo ri-attuando il $avg funzionalità esistenti, e quindi in quanto tale operatore è disponibile, è quello che dovrebbe essere usato.

Problemi correlati