Search code examples
arraysmongodbmongodb-queryaggregate-functions

Query mongodb: aggregate with $avg and $unwind in array


I need a query to return the avgRating to me, so I'm trying to run this query:

db.EVALUATION.aggregate([
  {
    $match: {
      _id: 84820
    }
  },
  {
    $unwind: "$reviews"
  },
  {
    $group: {
      _id: "$_id",
      avgRating: { $avg: "$reviews.rating" }
    }
  }
])

In real life the return needs to be avgRating: 1.1875 (I tested this in python). But, my return is avgRating: 1

example:

{
  _id: 84820,
  avgRating: 1
}

How i can fix this? I don't know if mongoDB is automatically rounding the result to 1... If so, how can I make it display the full decimal?

----code python----

sum= 0
counter= 0

for review in reviews:
   rating = review["rating"]  
   sum += rating 
   counter  += 1

media = sum/ counter

print(media)

Document:

{
  "_id": 84820,
  "name": "Boula",
  "location": {
    "city": "Paris",
  },
  "category": "restaurant",
  "description": "",
  "reviews": [
    {
      "wordsCount": 14,
      "rating": 0,
      "polarity": 10
    },
    {
      "wordsCount": 11,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 16,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 7,
      "rating": 0,
      "polarity": 10
    },
    {
      "wordsCount": 19,
      "rating": 0,
      "polarity": 10
    },
    {
      "wordsCount": 6,
      "rating": 0,
      "polarity": 10
    },
    {
      "wordsCount": 2,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 5,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 18,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 5,
      "rating": 0,
      "polarity": 5
    },
    {
      "wordsCount": 4,
      "rating": 0,
      "polarity": 10
    },
    {
      "wordsCount": 60,
      "rating": 5,
      "polarity": 10
    },
    {
      "wordsCount": 47,
      "rating": 2,
      "polarity": 5
    },
    {
      "wordsCount": 18,
      "rating": 3,
      "polarity": 5
    },
    {
      "wordsCount": 18,
      "rating": 4,
      "polarity": 5
    },
    {
      "wordsCount": 6,
      "rating": 5,
      "polarity": 5
    }
  ],
  "nbReviews": 16
},

I try many query, like:

db.EVALUATION.aggregate([
  {
    $match: {
      _id: 84820
    }
  },
  {
    $unwind: "$reviews"
  },
  {
    $group: {
      _id: "$_id",
      avgRating: { $avg: { $toDouble: "$reviews.rating" } }
    }
  }
])
db.EVALUATION.aggregate([
  {
    $match: {
      _id: 84820
    }
  },
  {
    $unwind: "$reviews"
  },
  {
    $group: {
      _id: "$_id",
      sumRatings: { $sum: { $convert: { input: "$reviews.rating", to: "decimal" } } },
      countRatings: { $sum: 1 }
    }
  },
  {
    $project: {
      avgRating: { $divide: [ "$sumRatings", "$countRatings" ] }
    }
  }
])
db.EVALUATION.aggregate([
  {
    $match: {
      _id: 84820
    }
  },
  {
    $unwind: "$reviews"
  },
  {
    $group: {
      _id: "$_id",
      ratings: {
        $push: {
          $convert: {
            input: "$reviews.rating",
            to: "decimal",
            onError: 0,
            onNull: 0
          }
        }
      }
    }
  },
  {
    $project: {
      avgRating: { $avg: "$ratings" }
    }
  }
])

Solution

  • I think you may be overcomplicating this. $avg is probably sufficient, based on the documented behavior with arrays here. Sample command:

    db.collection.aggregate([
      {
        $project: {
          _id: 1,
          avgRating: {
            $avg: "$reviews.rating"
          }
        }
      }
    ])
    

    Yields this result:

    [
      {
        "_id": 84820,
        "avgRating": 1.1875
      }
    ]
    

    Playground demonstration here