John John
John John

Reputation: 1455

Calculation of the average with aggregate MongoDB

I am trying to calculate the average value of the ratings for a Product. Instead of calculating the average rating of the Product every time when we need the value. I calculate it every time someone rates it. It looks like I am not doing it the right way but I am not sure where my mistake is. Let me know what I am doing wrong. Update - Now I understand why I am not getting any results. I am using post('save') instead of post('updateOne') but the implementation of the latter one is not very straightforward in the docs. Very confusing.

import mongoose from 'mongoose';
const { ObjectId } = mongoose.Schema;

const ParentSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      trim: true,
      required: true,
      maxlength: 32,
      text: true,
    },
    slug: {
      type: String,
      unique: true,
      lowercase: true,
      index: true,
    },
    price: {
      type: Number,
      required: true,
      trim: true,
      maxlength: 32,
    },
    quantity: Number,
    ratings: [
      {
        star: Number,
        postedBy: { type: ObjectId, ref: 'User' },
      },
    ],
  },
  {
    timestamps: true,
  }
);

ParentSchema.statics.avgRating = async function (id) {
  try {
    const stats = await this.model('Product').aggregate([
      {
        $group: {
          _id: id,
          avgRating: { $avg: '$ratings.star' },
          nRatings: { $sum: 1 },
        },
      },
    ]);

    console.log({ stats });
  } catch (err) {
    console.log(`avgRating error from Post SAVE hook${err}`);
  }
};

// Call avgRating before save
ParentSchema.post('save', async function () {
  await this.constructor.avgRating(this._id);
});

export default mongoose.models.Product ||
  mongoose.model('Product', ParentSchema);

Upvotes: 1

Views: 953

Answers (2)

Wernfried Domscheit
Wernfried Domscheit

Reputation: 59446

Not clear what you are looking for, but I would simply use

this.model('Product').aggregate([
  { 
    $addFields: { 
      avgRating: { $avg: "$rating.star" }, 
      nRatings: { $size: "$rating" } 
    } 
  }
])

Upvotes: 1

varman
varman

Reputation: 8894

You can use $group with $avg

db.collection.aggregate([
  { "$match": { _id: "1" } },
  { $unwind: "$rating" },
  {
    "$group": {
      "_id": "$_id",
      "title": { "$first": "$title" },
      "rating": { "$push": "$rating" },
      avg: { $avg: "$rating.star" }
    }
  }
])

Working Mongo playground

Update 1.

You can easlily do with $addFields. Thanks to @Wernfried Domscheit

db.collection.aggregate([
  { "$match": { _id: "1" } },
  {
    "$addFields": {
      "average": { "$avg": "$rating.star" }
    }
  }
])

Working Mongo playground

Upvotes: 1

Related Questions