We will first introduce the structure and relationships between each collections, then we will be going through how to use $lookup in mongodb aggregation to get our desired output
- User Collection
- Expenses Category Collection
- Expenses Collection
- Relationship between each Collections
- Aggregation $lookup
- Clean up Response Output by using $project
Documents in User Collection
/* 1 */
{
"_id" : ObjectId("6173e5fe7fc8622cbc56a108"),
"name" : "Jeff",
"age" : 20,
"ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e6067fc8622cbc56a10a"),
"name" : "Jason",
"age" : 25,
"ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
Documents in Expenses Category Collection
/* 1 */
{
"_id" : ObjectId("6173e6577fc8622cbc56a10c"),
"label" : "Food",
"user_ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e6677fc8622cbc56a10e"),
"label" : "Entertainment",
"user_ref_id" : "457935c0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "84021f00-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("6173e69d7fc8622cbc56a112"),
"label" : "Transportation",
"user_ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("6173e6d07fc8622cbc56a114"),
"label" : "Monthly Installment",
"user_ref_id" : "4a4bcc70-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
Documents in Expenses Collection
/* 1 */
{
"_id" : ObjectId("6173e7677fc8622cbc56a116"),
"label" : "Mc Donald",
"value" : 11.5,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "1cacea50-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 2 */
{
"_id" : ObjectId("6173e7767fc8622cbc56a118"),
"label" : "KFC",
"value" : 15,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "25358420-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 3 */
{
"_id" : ObjectId("6173e78a7fc8622cbc56a11a"),
"label" : "Sushi King",
"value" : 35,
"expenses_category_ref_id" : "7a27b0d0-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "312787b0-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 4 */
{
"_id" : ObjectId("6173e7e07fc8622cbc56a11c"),
"label" : "karaoke",
"value" : 45,
"expenses_category_ref_id" : "84021f00-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "649c2330-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 5 */
{
"_id" : ObjectId("6173e7f87fc8622cbc56a11e"),
"label" : "Petrol Fee",
"value" : 65,
"expenses_category_ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "72f1b940-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 6 */
{
"_id" : ObjectId("6173e8047fc8622cbc56a120"),
"label" : "Car Maintenance Service",
"value" : 150,
"expenses_category_ref_id" : "a3e28580-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "7a206860-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 7 */
{
"_id" : ObjectId("6173e8147fc8622cbc56a122"),
"label" : "House Loan",
"value" : 1500,
"expenses_category_ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "83b31f30-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
/* 8 */
{
"_id" : ObjectId("6173e8237fc8622cbc56a124"),
"label" : "Insurance",
"value" : 230,
"expenses_category_ref_id" : "c2a40e80-33ed-11ec-aac1-dd97bcc28a8a",
"ref_id" : "8cc22710-33ee-11ec-aac1-dd97bcc28a8a",
"__v" : 0
}
Relationship between each Collections
As we can observe the diagram above, collection Expenses Category
is actually having child-to-parent relationship where it stored the user-ref-id
field which is equivalent to ref-id
in User
collection.
Moving on, we also have collection Expenses
which also having the child-to-parent relationship where it stored the expenses-category-ref-id
field which is equivalent to ref-id
in Expenses Category
collection.
Aggregation $lookup
Controller below is belongs to the route http://localhost:3000/user/:id
where the parameter id
is the ref_id
of the User
|
|
Result
This case, we are using the 457935c0-33ed-11ec-aac1-dd97bcc28a8a
ref_id from the user called Jeff to pass as the route parameter.
The expected result will be => [response.json]
Explanations
- Import all the necessary Models such as User, Expenses Category and Expenses
- Get and access the route parameter
:id
which is equivalent to the Userref_id
- Apply aggregation method from the
User collection
- [Code Line 10 ~ 14]
- Find the relavant document by using $match operator and comparing the route param
:id
with ref_id fromUser collection
- This process is equivalent to
User.findOne({ref_id: req.param.id})
- Find the relavant document by using $match operator and comparing the route param
$lookup
syntax- from: < the target collection you want to join >
- let: < declare custom variable from your current Model. “In this case it’s referring User collection” >
- pipeline < Compare and query the condition to get desired output >
- as < specific the field name of the array output >
- [Code Line 15 ~ 24]
- First, we apply
$lookup
operator to join theexpenses-category
- Since,
expenses-category
model actually storinguser_ref_id
. We can then declare the variable calleduserRefId
which is referring to the Userref_id
and compare them later on - In order to use the custom variable we have declared earlier, we need to use the operator called $expr
- use
$match
operator to compare and filter out the result based onuserRefId
variable with theuser_ref_id
fromexpenses-category
collection underpipeline
operation - specific the query array output field name called
expenses-categories
in Line 40
- First, we apply
- [Code Line 25 ~ 38]
- Since
expenses
collection is nested insideexpenses-category
collection andexpenses-category
collection is nested insideuser
collection. We then now need to$lookup
one more round fromexpenses
collection toexpenses-category
collection before we moved out the operation - Apply
$lookup
operator to join theexpenses
collection - Since,
expenses
model actually storingexpenses_category_ref_id
. We can then declare the variable known ascategoryRefId
which is referring to the Expenses Categoryref_id
and compare them later on - use
$match
operator to compare and filter out the result based oncategoryRefId
variable with theexpenses_category_ref_id
fromexpenses
collection underpipeline
operation - specific the query array output field name called
expenses
- Since
- As now we know the ref_id is actually unique key in every documents while output data type from aggregation is Array. So we can just return the first element of the array or return empty object if the
ref_id
doesn’t existed.
Clean up Response Output by using $project
If you have seen the output response from the query above response.json. You may realize there are actually some unecessary fields we can skip or remove from our query.
Let’s see how we could handle it by using $project operator.
|
|
Result
The expected result will be => [filtered_response.json]
Explanations
- The output response is much cleaner after we used
$project
operator so remove and clean up the fields 0
is equals to hide while1
is equals to show- [Code Line 35 ~ 42] Expenses Collection
- Please take note that the
_id
field will be always show by default. Therefore, we can always assign0
to hide this particular field. - Then we can assign
1
to only show the necessary fields such asref_id
,label
andvalue
- Please take note that the
- The same concept applies to
Expenses Category Collection
in [Code Line 47 ~ 52] andUser Collection
in [Code Line 58 ~ 63]