我正在尝试使用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/这样的资源)?
答案 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" }
}
}
])