Mon goose возвращает среднее значение значений, сгруппированных по полям в поддокументе - PullRequest
1 голос
/ 11 февраля 2020

Я пытаюсь создать своего рода клон визга, используя команду express / mon goose, и мне нужно иметь возможность вернуть объект, содержащий средний балл для каждой из категорий рейтинга (например, глобальный, местоположение, еда, и атмосфера), каждый из другого поддокумента. Примерный ресторан выглядел бы примерно так (я убрал нерелевантные поля):

{ 
  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 Ответ

1 голос
/ 11 февраля 2020

Используя 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" }
        }
    }
])

Пн go Детская площадка

...