TLDR
How do I use MongoDB aggregation to include related documents from another collection that is linked by a one to many relationship?
In essence, what I want to do is to be able to fetch a list of Questions and include all flags associated with that question.
Update (11/07/2016): Solved with the solution posted below.
Update (05/07/2016): I have somewhat managed to get a list of questions with their associated flags by using a combination of $unwind, $lookup, $project
etc. The updated query is below.
Problem (05/07/2016): I am only able to fetch questions that have nested flags. I want to fetch all questions even if they do not have any flags.
I have two collections, one for content and one for content flags, as follows:
The schema for the content (question collection)
{
"_id" : ObjectId("..."),
"slug" : "a-sample-title",
"content" : "Some content.",
"title" : "A Sample Title.",
"kind" : "Question",
"updated" : ISODate("2016-06-08T08:54:26.104Z"),
"isPublished" : true,
"isFeatured" : false,
"flags" : [
ObjectId("<id_of_flag_one>"),
ObjectId("<id_of_flag_two>")
],
"answers" : [
ObjectId("..."),
ObjectId("...")
],
"related" : [],
"isAnswered" : true,
"__v" : 4
}
The shcema for the flags (flags collection)
{
"_id" : ObjectId("..."),
"flaggedBy" : ObjectId("<a_users_id>"),
"type" : "like",
"__v" : 0
}
In the above, a question can have many flags and a flag can only have one question. What I want to do is return all flags for a question when I query the question collection. I have tried doing this using aggregation with some luck.
Here is the updated query that I am using (05/07/2016)
fetchQuestions: (permission, params) => {
return new Promise((resolve, reject) => {
let query = Question.aggregate([
{
$lookup: {
from: 'users',
localField: 'author',
foreignField: '_id',
as: 'authorObject'
}
},
{
$unwind: '$authorObject'
},
{
$unwind: '$flags'
},
{
$lookup: {
from: 'flags',
localField: 'flags',
foreignField: '_id',
as: 'flagObjects'
}
},
{
$unwind: '$flagObjects'
},
{
$group: {
_id: {
_id: '$_id',
title: '$title',
content: '$content',
updated: '$updated',
isPublished: '$isPublished',
isFeatured: '$isFeatured',
isAnswered: '$isAnswered',
answers: '$answers',
author: '$authorObject'
},
flags: {
$push: '$flags'
},
flagObjects: {
$push: '$flagObjects'
}
}
},
{
$project: {
_id: 0,
_id: '$_id._id',
title: '$_id.title',
content: '$_id.content',
updated: '$_id.updated',
isPublished: '$_id.isPublished',
isFeatured: '$_id.isFeatured',
author: {
fullname: '$_id.author.fullname',
username: '$_id.author.username'
},
flagCount: {
$size: '$flagObjects'
},
answersCount: {
$size: '$_id.answers'
},
flags: '$flagObjects',
wasFlagged: {
$cond: {
if: {
$gt: [
{
$size: '$flagObjects'
},
0
]
},
then: true,
else: false
}
}
}
},
{
$sort: {
updated: 1
}
},
{
$skip: 0
},
// {
// $limit: 110
// }
])
.exec((error, result) => {
if(error) reject(error);
else resolve(result);
});
});
},
I have tried using other aggregation operators like $unwind
and $group
but the result set comes back with five items or less, and I am finding it difficult to grasp the concept of how these should all work together to get me what I need.
This is the response I am getting and it is exactly what I need. The only problem is, as described above, that I am only getting questions that have flags and not all questions.
"questions": [
{
"_id": "5757dd42d0c2ae292f76f11a",
"flags": [
{
"_id": "5774e0a81f2874821f71ace8",
"flaggedBy": "57569d02d0c2ae292f76f0f5",
"type": "concern",
"__v": 0
},
{
"_id": "577a0f5414b834372a6ac772",
"flaggedBy": "5756aa79d0c2ae292f76f0f8",
"type": "concern",
"__v": 0
}
],
"title": "A question for the landing page.",
"content": "This is a question that will appear on the landing page.",
"updated": "2016-06-08T08:54:26.104Z",
"isPublished": true,
"isFeatured": false,
"author": {
"fullname": "Matt Finucane",
"username": "matfin-386829"
},
"flagCount": 2,
"answersCount": 2,
"wasFlagged": true
},
...,
...,
...
]