Search code examples
mongodbmeteoraverage

inserting new field with $avg mongodb


Every time user rates some post from 1 to 5, that number is written (as property value) with userId as property name.

"starRatingByUser" : {
    "iZxSjCduTjfCQbmf9" : 3,
    "LvBr6a427ofuvXFMp" : 4,
    "gfhfhfh98rtgfXFft" : 5
}

Is it possible to insert (update) new field ("starRatingAverage") with average of all ratings every time users add or updates rating?

Example:

"starRatingAverage": 4,
"starRatingByUser" : {
    "iZxSjCduTjfCQbmf9" : 3,
    "LvBr6a427ofuvXFMp" : 4,
    "gfhfhfh98rtgfXFft" : 5
}

I have this method:

Recipes.update(
  { _id: recipeId },
  { $set: { ["starRatingByUser." + this.userId]: star }}
)

[example image] : https://i.sstatic.net/CuWbR.png


Solution

  • There is an option to update using aggregation in mongodb. Update-documents-with-aggregation-pipeline

    • The following update query sets to two args. First is to find the document, second is to do the aggregation

    In aggregation

    • convert the object into array using $objectToArray to calculate the total
    • get the size of the array using $size and calculate the total using $reduce
    • $project to remove unwanted fields

    the script is

    db.colelction.updateOne(
      {_id:1},
    [
      {
        $addFields: {
          stu: {
            "$objectToArray": "$starRatingByUser"
          }
        }
      },
      {
        $addFields: {
          size: {
            $size: "$stu"
          },
          total: {
            $reduce: {
              input: "$stu",
              initialValue: 0,
              in: {
                $add: [
                  "$$this.v",
                  "$$value"
                ]
              }
            }
          }
        }
      },
      {
        "$project": {
          starRatingByAverage: {
            "$divide": [
              "$total",
              "$size"
            ]
          },
          starRatingByUser: 1
        }
      }
    ])
    

    This query is checked and working fine