Reputation: 537
I have this two schema:
module.exports = mongoose.model('Article', {
title : String,
text : String,
lang : { type: String, default: 'en'},
user : { type : mongoose.Schema.Types.ObjectId, ref: 'User' },
});
var userSchema = mongoose.Schema({
email : String,
name : String,
rating : [{
_id: false,
articleID: {type: mongoose.Schema.Types.ObjectId, ref: 'Article'},
rate: Number
}]
});
module.exports = mongoose.model('User', userSchema);
and i want to calculate the average rating of an user (the average on all rating on its articles).
I tried this:
User.aggregate([
{ $unwind: "$rating" },
{
"$lookup": {
"from": "Article",
"localField": "rating.articleID",
"foreignField": "_id",
"as": "article-origin"
}
}//,
//{ $match: { "article-origin.user" : mongoose.Types.ObjectId(article.user) } }//,
//{ $group : {_id : "$rating.articleID", avgRate : { $avg : "$rating.rate" } } }
]).exec(function (err,result) {
console.log(err);
console.log(JSON.stringify(result));
});
but without success, the lockup always return the field article-origin null.
result:{"_id":"590747e1af02570769c875dc","name":"name","email":"email","rating":{"rate":5,"articleID":"59074a357fe6a307981e7925"},"__v":0,"article-origin":[]}]
Why this is not working ?
Upvotes: 3
Views: 2218
Reputation: 103455
Certainly no need for the $lookup
operator since the group aggregation operation does not make use of the documents from the articles
collection, it only needs a single field i.e. articleID
for grouping.
There are two ways you can go about this. If your MongoDB server version is 3.4 or greater, then the $divide
, $reduce
and $size
operators can be applied here to calculate the average without resorting
to flatten the rating array first which can have some performance ramifications if the array is large.
Consider running the following pipeline:
User.aggregate([
{ "$match": { "_id" : mongoose.Types.ObjectId(article.user) } },
{
"$addFields": {
"avgRate": {
"$divide": [
{
"$reduce": {
"input": "$rating",
"initialValue": 0,
"in": { "$sum": ["$$value", "$$this.rate"] }
}
},
{
"$cond": [
{ "$ne": [{ "$size": "$rating" }, 0] },
{ "$size": "$rating" },
1
]
}
]
}
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});
If using MongoDB version 3.2 then you would need to $unwind
the rating array first:
User.aggregate([
{ "$match": { "_id" : mongoose.Types.ObjectId(article.user) } },
{ "$unwind": "$rating" },
{
"$group": {
"_id": "$_id",
"avgRate": { "$avg": "$rating.rate" }
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});
If for some reason you need the $lookup
operation, you need to reference the collection name, not the model name, thus the correct aggregate operation should be
User.aggregate([
{ "$unwind": "$rating" },
{
"$lookup": {
"from": "articles", /* collection name here, not model name */
"localField": "rating.articleID",
"foreignField": "_id",
"as": "article-origin"
}
},
{ "$match": { "article-origin.user" : mongoose.Types.ObjectId(article.user) } },
{
"$group": {
"_id": "$_id",
"avgRate": { "$avg": "$rating.rate" }
}
}
]).exec(function (err, result) {
console.log(err);
console.log(JSON.stringify(result));
});
Upvotes: 1