mongodb $lookup 3 level nested document - mongodb

I'm new to mongo and struggling mightily with the following.
In my mongodb database, there are 3 collections and structured as below.
lv1:
{
"_id": ObjectId("58650f1abbf1cd8804d0abde"),
"name": "lv1_aaa"
}
lv2:
{
"_id": ObjectId("5ba45de41e78c7eb3fdfbfa6"),
"lv1_id": ObjectId("58650f1abbf1cd8804d0abde"),
"name": "lv2_bbb"
}
lv3:
{
"_id": ObjectId("5ba45de41e78c7eb3fdfbfa6"),
"lv1_id": ObjectId("58650f1abbf1cd8804d0abde"),
"lv2_id": ObjectId("58d8c3e1bbf1cd7436117bd6"),
"name": "lv3_ccc"
}
How can I get a data structure below use $lookup
[
{
"_id": ObjectId("58650f1abbf1cd8804d0abde"),
"name": "lv1_aaa",
"children": [
{
"_id": ObjectId("5ba45de41e78c7eb3fdfbfa6"),
"lv1_id": ObjectId("58650f1abbf1cd8804d0abde"),
"name": "lv2_bbb",
"children": [
{
"_id": ObjectId("5ba45de41e78c7eb3fdfbfa6"),
"lv1_id": ObjectId("58650f1abbf1cd8804d0abde"),
"lv2_id": ObjectId("58d8c3e1bbf1cd7436117bd6"),
"name": "lv3_ccc"
},
......
]
},
......
]
},
......
]
Any help would be greatly appreciated!

You can try below aggregation with mongodb 3.6 and above
db.lv1.aggregate([
{ "$sort": { _id: 1 } },
{ "$lookup": {
"from": "lv2",
"let": { "lv1_id": "$_id" },
"pipeline": [
{ "$sort": { index: 1 } },
{ "$match": { "$expr": { "$eq": [ "$lv1_id", "$$lv1_id" ] } } },
{ "$lookup": {
"from": "lv3",
"let": { "lv2_id": "$_id" },
"pipeline": [
{ "$sort": { index: 1 } },
{ "$match": { "$expr": { "$eq": [ "$lv2_id", "$$lv2_id" ] } } }
],
"as": "children"
}}
],
"as": "children"
}}
]);

Related

How to group same record into multiple groups using mongodb aggregate pipeline

I have a two collections.
OrgStructure (visualise this as a tree structure)
Example Document:
{
"id": "org1",
"nodes": [
{
"nodeId": "root",
"childNodes": ["child1"]
},
{
"nodeId": "child1",
"childNodes": ["child2"]
},
{
"nodeId": "child2",
"childNodes": []
}
]
}
Activity
Example Document:
[
{
"id":"A1",
"orgUnit": "root"
},
{
"id":"A2",
"orgUnit": "child1"
},
{
"id":"A3",
"orgUnit": "child2"
}
]
Now my expectation is to group activities by orgUnit such a way that by considering the child nodes as well.
Here i don't want to do a lookup and i need to consider one OrgStructure document as an input, so that i can construct some condition using the document such a way that the query will return the below result.
Expected result
[
{
"_id": "root",
"activities": ["A1","A2","A3"]
},
{
"_id": "child1",
"activities": ["A2","A3"]
},
{
"_id": "child2",
"activities": ["A3"]
}
]
So im ecpecting an aggregate query something like this
{
"$group": {
"_id": {
"$switch": {
"branches": [
{
"case": {"$in": ["$orgUnit",["root","child1","child2"]]},
"then": "root"
},
{
"case": {"$in": ["$orgUnit",["child1","child2"]]},
"then": "child1"
},
{
"case": {"$in": ["$orgUnit",["child2"]]},
"then": "child2"
}
],
"default": null
}
}
}
}
Thanks in advance!
You will need 2 steps:
create another collection nodes for recursive lookup. The original OrgStructure is hard to perform $graphLookup
db.OrgStructure.aggregate([
{
"$unwind": "$nodes"
},
{
"$replaceRoot": {
"newRoot": "$nodes"
}
},
{
$out: "nodes"
}
])
Perform $graphLookup on nodes collection to get all child nodes. Perform $lookup to Activity and do some wrangling.
db.nodes.aggregate([
{
"$graphLookup": {
"from": "nodes",
"startWith": "$nodeId",
"connectFromField": "childNodes",
"connectToField": "nodeId",
"as": "nodesLookup"
}
},
{
"$lookup": {
"from": "Activity",
"let": {
nodeId: "$nodesLookup.nodeId"
},
"pipeline": [
{
$match: {
$expr: {
$in: [
"$orgUnit",
"$$nodeId"
]
}
}
},
{
$group: {
_id: "$id"
}
}
],
"as": "activity"
}
},
{
$project: {
_id: "$nodeId",
activities: "$activity._id"
}
}
])
Here is the Mongo playground for your reference.

How do I use a different condition if there are empty results on the condition of a $match of a $lookup?

Using the mongoDb Aggregation framework; suppose I wanted to $lookup a set of results in another collection with a condition that if returned no results - would then return the results of another condition. This is what I have.
srp collection
[
{
"_id": ObjectId("5fb6727790f41fef3ee7db87"),
"dates_e": {
"from": ISODate("2021-10-01"),
"to": ISODate("2021-10-03")
}
},
{
"_id": ObjectId("5f034bfa4c0000abdfc7df2e"),
"dates_e": {
"from": ISODate("2021-03-10"),
"to": ISODate("2021-03-15")
}
},
..
]
uth collection
[
{
"_id": ObjectId("5fb6727790f41fef3ee7db88"),
"dateTime": ISODate("2021-10-01"),
"res": 1.7
},
{
"_id": ObjectId("5fb6727790f41fef3ee7db89"),
"dateTime": ISODate("2021-10-02"),
"res": 0.5
},
..
]
The aggregation query (on the srp collection):
[
{
$match: { "_id": ObjectId("5fb6727790f41fef3ee7db87") }
},
{
$lookup: {
"from": "uth",
"let": {
"fromDate": "$dates_e.from",
"toDate": "$dates_e.to"
},
"pipeline": [
$match: {
$expr: {
$and: [
{
$gte: ["$varData_e.dateTime", "$$fromDate"]
},
{
$lt: ["$varData_e.dateTime", "$$toDate"]
}
]
}
}
],
"as": "uth_e"
}
}
]
Which would return:
[
{
"_id": ObjectId("5fb6727790f41fef3ee7db87"),
"dates_e": {
"from": ISODate("2021-10-01"),
"to": ISODate("2021-10-03")
},
"uth_e": [
{
"_id": ObjectId("5fb6727790f41fef3ee7db88"),
"dateTime": ISODate("2021-10-01"),
"res": 1.7
},
{
"_id": ObjectId("5fb6727790f41fef3ee7db89"),
"dateTime": ISODate("2021-10-02"),
"res": 0.5
},
{
"_id": ObjectId("5fb6727790f41fef3ee7db90"),
"dateTime": ISODate("2021-10-03"),
"res": 2.8
}
]
]
So this works just fine. However if the $match was "_id": ObjectId("5f034bfa4c0000abdfc7df2e") and there weren't any results returned (on the $lookup from uth) then I would like to return a set of results for a broader condition:
[
{
$match: { "_id": ObjectId("5f034bfa4c0000abdfc7df2e") }
},
{
$lookup: {
"from": "uth",
"let": {
"fromDate": "$dates_e.from",
"toDate": "$dates_e.to"
},
"pipeline": [
$match: {
$expr: {
$gte: ["$varData_e.dateTime", "$$fromDate"]
}
}
],
"as": "uth_e"
}
}
]
Any help appreciated!

$lookup in nested array

I need a MongoDB query to return the aggregation result from a collection of events, users and confirmations.
db.events.aggregate([
{
"$match": {
"_id": "1"
}
},
{
"$lookup": {
"from": "confirmations",
"as": "confirmations",
"let": {
"eventId": "$_id"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$eventId",
"$$eventId"
]
}
}
},
{
"$lookup": {
"from": "users",
"as": "user",
"let": {
"userId": "$confirmations.userId"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$userId"
]
}
}
},
]
},
},
]
}
}
])
Desired
[
{
"_id": "1",
"confirmations": [
{
"_id": "1",
"eventId": "1",
"user": {
"_id": "1",
"name": "X"
},
"userId": "1"
},
{
"_id": "2",
"eventId": "1",
"user": {
"_id": "2",
"name": "Y"
},
"userId": "2"
}
],
"title": "foo"
}
]
Everything works except the embedded user in confirmations array. I need the output to show the confirmations.user, not an empty array.
Playgound: https://mongoplayground.net/p/jp49FW59WCv
You made mistake in variable declaration of inner $lookup. Try this Solution:
db.events.aggregate([
{
"$match": {
"_id": "1"
}
},
{
$lookup: {
from: "confirmations",
let: { "eventId": "$_id" },
pipeline: [
{
$match: {
"$expr": {
$eq: ["$eventId", "$$eventId"]
}
}
},
{
$lookup: {
from: "users",
let: { "userId": "$userId" },
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", "$$userId"]
}
}
}
],
as: "user"
}
},
{ $unwind: "$user" }
],
as: "confirmations"
}
}
])
Also instead of $unwind of user inside inner $lookup you can use:
{
$addFields: {
user: { $arrayElemAt: ["$user", 0] }
}
}
since $unwind will not preserve empty results from previous stage by default.

Get Count of specific field mongodb

I am using below query to get combined data from users and project collections:
db.collection.aggregate([
{
"$group": {
"_id": "$userId",
"projectId": { "$push": "$projectId" }
}
},
{
"$lookup": {
"from": "users",
"let": { "userId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userId" ] }}},
{ "$project": { "firstName": 1 }}
],
"as": "user"
}
},
{ "$unwind": "$user" },
{
"$lookup": {
"from": "projects",
"let": { "projectId": "$projectId" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$projectId" ] }}},
{ "$project": { "projectName": 1 }}
],
"as": "projects"
}
}
])
and it results like below:
[
{
"_id": "5c0a29e597e71a0d28b910aa",
"projectId": [
"5c0a2a8897e71a0d28b910ac",
"5c0a4083753a321c6c4ee024"
],
"user": {
"_id": "5c0a29e597e71a0d28b910aa",
"firstName": "Amit"
},
"projects": [
{
"_id": "5c0a2a8897e71a0d28b910ac",
"projectName": "LN-PM"
},
{
"_id": "5c0a4083753a321c6c4ee024",
"projectName": "fallbrook winery"
}
]
},
{
"_id": "5c0a29c697e71a0d28b910a9",
"projectId": [
"5c0a4083753a321c6c4ee024"
],
"user": {
"_id": "5c0a29c697e71a0d28b910a9",
"firstName": "Rajat"
},
"projects": [
{
"_id": "5c0a4083753a321c6c4ee024",
"projectName": "fallbrook winery"
}
]
}
]
Now i have another table "Worksheets" and want to include hours field in projects Array, which will be calculated from the worksheets table by specifying the projectId which is _id in the projects array. It will be find in worksheet table and hours will be incremented how many times this _id has in worksheets table. Below is my worksheet collection:
{
"_id" : ObjectId("5c0a4efa91b5021228681f7a"),
"projectId" : ObjectId("5c0a4083753a321c6c4ee024"),
"hours" : 8,
"userId" : ObjectId("5c0a29c697e71a0d28b910a9"),
"__v" : 0
}
{
"_id" : ObjectId("5c0a4f4191b5021228681f7c"),
"projectId" : ObjectId("5c0a2a8897e71a0d28b910ac"),
"hours" : 6,
"userId" : ObjectId("5c0a29e597e71a0d28b910aa"),
"__v" : 0
}
The result will look like below:
{
"_id": "5c0a29c697e71a0d28b910a9",
"projectId": [
"5c0a4083753a321c6c4ee024"
],
"user": {
"_id": "5c0a29c697e71a0d28b910a9",
"firstName": "Rajat"
},
"projects": [
{
"_id": "5c0a4083753a321c6c4ee024",
"projectName": "fallbrook winery",
"hours":8
}
]
}
You can use below aggregation
$lookup 3.6 nested syntax allows you to join nested collection inside the $lookup pipeline. You can perform all the aggregation inside the nested $lookup pipline
db.collection.aggregate([
{ "$group": {
"_id": "$userId",
"projectId": { "$push": "$projectId" }
}},
{ "$lookup": {
"from": "users",
"let": { "userId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$userId" ] }}},
{ "$project": { "firstName": 1 }}
],
"as": "user"
}},
{ "$unwind": "$user" },
{ "$lookup": {
"from": "projects",
"let": { "projectId": "$projectId" },
"pipeline": [
{ "$match": { "$expr": { "$in": [ "$_id", "$$projectId" ] }}},
{ "$lookup": {
"from": "worksheets",
"let": { "projectId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$projectId", "$$projectId" ] }}},
{ "$group": {
"_id": "$projectId",
"totalHours": { "$sum": "$hours" }
}}
],
"as": "workHours"
}}
{ "$project": {
"projectName": 1,
"hours": { "$arrayElemAt": ["$workHours.totalHours", 0] }
}}
],
"as": "projects"
}}
])

Populate to the deep level using $lookup mongodb

I am using $lookup to join two collections and get data from below query:
let condition = {status:{$ne:config.PROJECT_STATUS.completed}, assignId:mongoose.Types.ObjectId(req.params.id)};
Project.aggregate([
{
$match: condition
},
{
"$group":{
"_id": "$_id"
}
},
{
"$lookup": {
"from": "worksheets",
"let": { "projectId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$projectId", "$$projectId" ] }}},
{ "$group": {_id:"$projectId", totalHours:{"$sum": "$hours"}}},
{
"$lookup": {
"from": "projects",
"let": { "projectId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$projectId" ] }}},
{ "$project": { "projectName": 1,"upworkdId":1,"status":1,"developers":1,"hoursApproved":1 }}
],
"as": "project"
}
}
],
"as": "projects"
}
}
])
.then((data)=>{
res.json(data);
})
And above query is giving me below result:
[
{
"_id": "5c0a4083753a321c6c4ee024",
"projects": [
{
"_id": "5c0a4083753a321c6c4ee024",
"totalHours": 11,
"project": [
{
"_id": "5c0a4083753a321c6c4ee024",
"hoursApproved": 24,
"developers": [
"5c0a29c697e71a0d28b910a9"
],
"projectName": "fallbrook winery",
"status": "pending"
}
]
}
]
}
]
Now i want to populate the developers subfield inside the project Array. How can i modify the above code to get that.
You can use $lookup one more deep level with the developers array.
Something like this
Project.aggregate([
{ "$match": condition },
{ "$group": { "_id": "$_id" }},
{ "$lookup": {
"from": "worksheets",
"let": { "projectId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$projectId", "$$projectId"] } } },
{ "$group": { "_id": "$projectId", "totalHours": { "$sum": "$hours" } }},
{ "$lookup": {
"from": "projects",
"let": { "projectId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$_id", "$$projectId"] } } },
{ "$lookup": {
"from": "developers",
"let": { "developers": "$developers" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$_id", "$$developers"] } } },
],
"as": "developers"
}},
{ "$project": {
"projectName": 1, "upworkdId": 1, "status": 1, "developers": 1, "hoursApproved": 1
}}
],
"as": "project"
}}
],
"as": "projects"
}}
])