猫鼬返回按子文档中的字段分组的值的平均值

时间:2020-02-11 16:58:04

标签: database mongodb mongoose aggregation-framework

我正在尝试使用Express /猫鼬构建一种yelp克隆,并且需要能够返回包含每个评分类别(例如:全球,位置,食物和氛围)平均得分的对象,每个都来自不同的审阅子文档。样本餐厅看起来像这样(我已经删除了不相关的字段):

{ 
  name: 'Test Restaurant',
  reviews: [ 
    { 
      _id: 5e42c12e903eac188077dca5, 
      rating: {
        global: 10,
        location: 5,
        food: 8,
        ambient: 6
        } 
    },
    { 
      _id: 5e42c12e903eac188077dca6, 
      rating: {
        global: 5,
        location: 6,
        food: 4,
        ambient: 2
        } 
    },
    { 
      _id: 5e42c12e903eac188077dca7, 
      rating: {
        global: 9,
        location: 3,
        food: 5,
        ambient: 1
        } 
    },
  ]
}

到目前为止,我已经可以使用以下虚拟方法做到这一点,但是我认为如果评论数量足够大,这种方法将迅速变得无效:

restaurantSchema.virtual("averageRatings").get(function() {
  const restaurant = this;
  const ratings = restaurant.reviews.map(review => review.rating);
  const ratingsArrays = {};
  const averageRatings = {};
  const categories = [
    "global",
    "food",
    "ambient",
    "location"
  ];

  //Loop for every category, create new array with the ratings from each review per category, and filter out null ratings
  categories.forEach(category => {
    ratingsArrays[category] = ratings
      .map(rating => rating[category])
      .filter(value => value != null);

  //Calculate average for each category, and return undefined where there are not ratings
    averageRatings[category] =
      ratingsArrays[category].reduce((acc, current) => acc + current, 0) /
        ratingsArrays[category].length || undefined;
  });
  return averageRatings;
});

现在,我已经看到使用MongoDB聚合框架来实现此目的的更有效/更正确的方法,但是即使我仔细阅读了手册,并发现了针对特定情况的一些很好的答案,我仍然没有能够在这种特殊情况下解决它。

可以帮助我设计一个大概的管道吗?您是否知道有比官方手册更友好的方法来学习MongoDB查询/聚合语法(对于SQL:https://sqlbolt.com/这样的资源)?

1 个答案:

答案 0 :(得分:1)

使用Aggregation Framework,您可以动态读取rating键来计算平均值。尝试从$objectToArray开始,然后按检索到的键(k)进行分组。最终,您需要运行$arrayToObject的反向操作:

db.collection.aggregate([
    {
        $unwind: "$reviews"
    },
    {
        $project: {
            values: { $objectToArray: "$reviews.rating" }
        }
    },
    {
        $unwind: "$values"
    },
    {
        $group: {
            _id: { _id: "$_id", k: "$values.k" },
            sum: { $sum: "$values.v" },
            count: { $sum: 1 }
        }
    },
    {
        $group: {
            _id: "$_id._id",
            averages: { $push: { k: "$_id.k", v: { $divide: [ "$sum", "$count" ] } } }
        }
    },
    {
        $project: {
            avgReviews: { $arrayToObject: "$averages" }
        }
    }
])

Mongo Playground