【问题标题】:Mongoose return average of values grouped by fields in subdocumentMongoose 返回子文档中按字段分组的值的平均值
【发布时间】:2020-05-27 03:48:54
【问题描述】:

我正在尝试使用 express/mongoose 构建一种 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/)?

【问题讨论】:

    标签: database mongodb mongoose aggregation-framework


    【解决方案1】:

    使用聚合框架,您可以通过读取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

    【讨论】:

    • 这非常有效。谢谢你。你有什么推荐的资源来学习这些东西吗? (官方手册除外……)
    • @Dijkie85 我相信官方文档的质量真的很好——很多例子等等。你只需要浏览所有的阶段和操作符来熟悉AF的功能。
    • 很抱歉再次打扰您@mickl,但如果评论不是太困难,您能否指出将这个聚合作为餐厅对象的一部分返回的方式(即,不仅仅是聚合本身),以及它的其他属性?很抱歉这次勺子喂食;我有很多东西要学...
    • @Dijkie85 这是name 的操作方法:mongoplayground.net/p/25HlCkh1nR-,-您可以对其他字段采用相同的方法
    • 太棒了!再次感谢你。可以肯定的是,这也适用于一系列餐厅吗?
    猜你喜欢
    • 2021-05-07
    • 1970-01-01
    • 2015-06-02
    • 2016-09-27
    • 2015-07-29
    • 1970-01-01
    • 1970-01-01
    • 2021-07-27
    相关资源
    最近更新 更多