Populate specific fields in $lookup - mongodb

I am using aggregate to group and populate the result like below:
{
"$group": {
"_id": "$userId",
"projectId": { "$push": "$projectId" }
}
},
{
"$lookup": {
"from": "users",
"localField": "_id",
"foreignField": "_id",
"as": "user"
}
},
{ $unwind:"$user" },
{
"$lookup": {
"from": "projects",
"localField": "projectId",
"foreignField": "_id",
"as": "projects"
}
}
But i want to populate specific fields from that result For this I tried
$project, But it combining projectId into one array and projectName into another array.Below is my result json:
[
{
"_id": "5c0a29e597e71a0d28b910aa",
"projectId": [
"5c0a2a8897e71a0d28b910ac",
"5c0a4083753a321c6c4ee024"
],
"user": {
"_id": "5c0a29e597e71a0d28b910aa",
"firstName": "Amit"
"lastName": "kumar",
"type": "developer",
"status": "active"
},
"projects": [
{
"_id": "5c0a2a8897e71a0d28b910ac",
"skypeId": "",
"projectName": "LN-PM",
"status": "ongoing",
"assignId": "5c0a2a0a97e71a0d28b910ab"
},
{
"_id": "5c0a4083753a321c6c4ee024",
"skypeId": "",
"status": "pending",
"assignId": "5c0a2a0a97e71a0d28b910ab"
}
]
}
]
Now i want to get the only "firstName and _id" field from user field and "projectName and _id" field from the projects field

You can use below aggregation with mongodb 3.6 and above
With the newer $lookup syntax you can use $projection inside the $lookup pipeline
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"
}}
])

Related

Mongodb , "JOIN" a nested object

I need to join the faults array
{
"_id": "99812930-37CE-456F-A9D9-837E9E3F712A",
"faultsChanged": [
{
"_id": "7C628A46-7E80-4615-8B08-10C5E9A6B1D7",
"faults": [
"BF221A71-0217-42E7-B853-53112EDA9694",
"E4A54172-7E93-49C4-840B-8E6116116979"
],
"isDeleted": false,
"partition": "indego",
"sessionUuid": "A83CE9A1-7539-493F-8BA4-6FBE25B18B57",
"source": "1",
"timestamp": {
"$date": {
"$numberLong": "1630603342700"
}
},
"unmigratedNote": null,
"uuid": "7C628A46-7E80-4615-8B08-10C5E9A6B1D7"
}
]
}
So that it gets replaced by a document from another collection OOFaultEntry.
New to the aggregation pipeline, I have tried
{$lookup:{
from: "OOFaultEntry",
localField: 'faultsChanged.faults',
foreignField: "_id",
let:{faults:"$faultsChanged.faults"},
pipeline:[],
as:"faults"
}}
but this just created a key on the result and was not embedded inside of each respective faultChanged object
expected result
OOFaultEntry would be JOINED on _id . So ,it should look something like
{
"_id": "99812930-37CE-456F-A9D9-837E9E3F712A",
"faultsChanged": [
{
"_id": "7C628A46-7E80-4615-8B08-10C5E9A6B1D7",
"faults": [
{
"_id": "BF221A71-0217-42E7-B853-53112EDA9694",
"some_key": "value"
},
{
"_id": "E4A54172-7E93-49C4-840B-8E6116116979",
"some_key": "value"
}
],
"isDeleted": false,
"partition": "indego",
"sessionUuid": "A83CE9A1-7539-493F-8BA4-6FBE25B18B57",
"source": "1",
"timestamp": {
"$date": {
"$numberLong": "1630603342700"
}
},
"unmigratedNote": null,
"uuid": "7C628A46-7E80-4615-8B08-10C5E9A6B1D7"
}
]
}
One way would be $unwind the faultsChanged array first. Perform the $lookup and regroup the results.
db.Faults.aggregate([
{
"$unwind": "$faultsChanged"
},
{
"$lookup": {
"from": "00FaultEntry",
"localField": "faultsChanged.faults",
"foreignField": "_id",
"as": "faultsChanged.faults"
}
},
{
"$group": {
"_id": "$_id",
"faultsChanged": {
$push: "$faultsChanged"
}
}
}
])
Mongo Playground

How to add embedded field with matching documents

I'm using Python with pymongo to query from the database.
I have 3 different collections:
1st:
# Projects collection
{
"_id": "A",
},
{
"_id": "B",
},
{
"_id": "C"
},
..
2nd:
# Episodes collection
{
"_id": "A/Episode01",
"project": "A",
"name": "Episode01"
},
{
"_id": "A/Episode02",
"project": "A",
"name": "Episode02"
},
{
"_id": "B/Episode01",
"project": "B",
"name": "Episode01"
},
..
3rd:
# Sequences collection
{
"_id": "A/Episode01/Sequence01",
"project": "A",
"episode": "Episode01",
"name": "Sequence01"
},
{
"_id": "A/Episode02/Sequence02",
"project": "A",
"episode": "Episode02",
"name": "Sequence02"
},
{
"_id": "B/Episode01/Sequence01",
"project": "B",
"episode": "Episode01",
"name": "Sequence01"
},
..
I want to use aggregate to query project A and get all of its corresponding episodes and sequences like this:
{
"_id": "A",
"episodes":
[
{
"_id": "A/Episode01",
"project": "A",
"name": "Episode01",
"sequences":
[
{
"_id": "A/Episode01/Sequence01",
"project": "A",
"episode": "Episode01",
"name": "Sequence01"
},
]
},
{
"_id": "A/Episode02",
"project": "A",
"name": "Episode02",
"sequences":
[
{
"_id": "A/Episode02/Sequence02",
"project": "A",
"episode": "Episode02",
"name": "Sequence02"
},
]
},
]
}
I can get as far as getting the proper episodes, but I'm not sure how to add an embed field for any matching sequences. Is it possible to do this all in a single pipeline query?
Right now my query is looking like this:
[
{"$match": {
"_id": "A"}
},
{"$lookup": {
"from": "episodes",
"localField": "_id",
"foreignField": "project",
"as": "episodes"}
},
{"$group": {
"_id": {
"_id": "$_id",
"episodes": "$episodes"}
}}
]
You can do like following
use $match to match the document
use uncorrelated queries to join two collection. But normal joining also possible as you have written. This is easier when we get some complex situations.
Mongo script is given below
[
{
"$match": {
"_id": "A"
}
},
{
$lookup: {
from: "Episodes",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$project",
"$$id"
]
}
}
},
{
$lookup: {
from: "Sequences",
let: {
epi: "$name"
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$episode",
"$$epi"
]
}
}
}
],
as: "sequences"
}
}
],
as: "episodes"
}
}
]
Working Mongo playground
Update 01
Using standard lookup
[
{
"$match": {
"_id": "A"
}
},
{
"$lookup": {
"from": "Episodes",
"localField": "_id",
"foreignField": "project",
"as": "episodes"
}
},
{
$unwind: "$episodes"
},
{
"$lookup": {
"from": "Sequences",
"localField": "episodes.name",
"foreignField": "episode",
"as": "episodes.sequences"
}
},
{
$group: {
_id: "$episodes._id",
episodes: {
$addToSet: "$episodes"
}
}
}
]
Working Mongo playground

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"
}}
])

Merge two $lookup collections in MongoDB

Here i am try to get aggregated result from my challenge collection with challengeusers and challengeusers has the user_id and i used $lookup to join the users too.
When i use this query, i am getting following output.
"challenges": [
{
"_id": "5b7bf6fd87ec106308d7e3c1",
"start_date": "2018-08-09T12:40:21.470Z",
"end_date": "2018-08-05T12:40:21.470Z",
"challnegedusers": [
{
"chalenge_id": "5b7bf6fd87ec106308d7e3c1",
"user_id": "5b75623db457045e3bb12e0a",
"status": 1
},
{
"user_id": "5b75643c0a97791bcc9ed64c",
"status": 1
},
{
"user_id": "5b756144b457045e3bb12e08",
"status": 1
}
],
"users": [
{
"_id": "5b756144b457045e3bb12e08",
"first_name": "XYZ"
},
{
"_id": "5b75623db457045e3bb12e0a",
"first_name": "BAC"
},
{
"_id": "5b75643c0a97791bcc9ed64c",
"first_name": "YTA"
}
]
}
]
But i want the challengeusers and users to merge in a single object.
Most of all i want the status of challengeusers with user's info.
expected output:
"challenges": [
{
"_id": "5b7bf6fd87ec106308d7e3c1",
"start_date": "2018-08-09T12:40:21.470Z",
"end_date": "2018-08-05T12:40:21.470Z",
"challnegedusers": [
{
"user_id": "5b75623db457045e3bb12e0a",
"status": 1,
"first_name": "BAC"
},
{
"user_id": "5b75643c0a97791bcc9ed64c",
"status": 1,
"first_name": "YTA"
},
{
"user_id": "5b756144b457045e3bb12e08",
"status": 1,
"first_name": "XYZ"
}
]
}
]
MongoDB Aggregate Query that i am using.
let challenges = await ChallengeModel.aggregate([
{ $match: criteria },
{ $lookup: {
from: 'challengeusers',
localField: '_id',
foreignField: 'challenge_id',
as: 'challnegedusers'
} },
{ $lookup: {
from: 'appusers',
localField: 'challnegedusers.user_id',
foreignField: '_id',
as: 'users'
} },
{ $sort: {created_at: -1}}
]);
You can try below aggregation in mongodb 3.6
ChallengeModel.aggregate([
{ "$match": criteria },
{ "$lookup": {
"from": "challengeusers",
"let": { "challengeusersId": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$challenge_id", "$$challengeusersId" ] } } },
{ "$lookup": {
"from": "appusers",
"let": { "user_id": "$user_id" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$user_id" ] } } },
],
"as": "user"
}},
{ "$unwind": "$user" },
{ "$addFields": { "first_name": "$user.first_name" }},
{ "$project": { "user": 0 }}
],
"as": "challnegedusers"
}}
])
And with your approach you can try this
ChallengeModel.aggregate([
{ "$match": criteria },
{ "$lookup": {
"from": "challengeusers",
"localField": "_id",
"foreignField": "challenge_id",
"as": "challnegedusers"
}},
{ "$unwind": "challnegedusers" },
{ "$lookup": {
"from": "appusers",
"localField": "challnegedusers.user_id",
"foreignField": "_id",
"as": "challnegedusers.user"
}},
{ "$unwind": "challnegedusers.user" },
{ "$addFields": { "challnegedusers.first_name": "$challnegedusers.user.first_name" }},
{ "$sort": { "created_at": -1 }},
{ "$group": {
"_id": "$_id",
"start_date": { "$first": "$start_date" }
"end_date": { "$first": "$end_date" },
"challnegedusers": { "$push": "$challnegedusers" }
}}
])