How to do $lookup in an array's field and add the foreign table's content in the same query? - mongodb

I am trying to make a query in which I have a db_task query that contains the task id and the users assigned to it. I have to fetch the user details which are present in db_user collection and add the incoming details into the same document.
db_task
{
"_id" : ObjectId("5d8b522d0cf2579c57bc8ce0"),
"users" : [
{
"user_id" : ObjectId("5d8b522d0cf2579e27bc8ce3"),
"is_finished" : false
},
{
"user_id" : ObjectId("5d6f6d25e079b9fb7d858236"),
"is_finished" : false
}
]
}
The users field the users who are assigned that task, I want to do a lookup on the db_user query which would get me the details inside the same embedded document.
db_user
{
"_id" : ObjectId("5d8b522d0cf2579e27bc8ce3"),
"first_name" : "Harry",
"last_name" : "Paul"
},
{
"_id" : ObjectId("5d6f6d25e079b9fb7d858236"),
"first_name" : "Aaron",
"last_name" : "Potter"
}
I tried to do $lookup on the db_user table with "users.user_id" but that would fetch me a new field and then I tried to concatenate those arrays with "$concatArrays" but the result still wasn't what I expected.
I want to get the output in a format something like this
db_task
{
"_id" : ObjectId("5d8b522d0cf2579c57bc8ce0"),
"users" : [
{
"user_id" : ObjectId("5d8b522d0cf2579e27bc8ce3"),
"is_finished" : false,
"user_info":{
"first_name" : "Harry",
"last_name" : "Paul"
}
},
{
"user_id" : ObjectId("5d6f6d25e079b9fb7d858236"),
"is_finished" : false,
"user_info":{
"first_name" : "Aaron",
"last_name" : "Potter"
}
}
]
}

Altough they're working, the provided solutions, with unwind and group, can be expensive in resources.
Here's a better solution in only two stages :
db.db_task.aggregate([
{
$lookup: {
from: "db_user",
localField: "users.user_id",
foreignField: "_id",
as: "usersInfos"
}
},
{
$project: {
users: {
$map: {
input: "$usersInfos",
as: "ui",
in: {
$mergeObjects: [
"$$ui",
{
$arrayElemAt: [
{
$filter: {
input: "$users",
as: "users",
cond: {
$eq: [
"$$users.user_id",
"$$ui._id"
]
}
}
},
0
]
}
]
}
}
}
}
}
])
Will output
[
{
"_id": ObjectId("5d8b522d0cf2579c57bc8ce0"),
"users": [
{
"_id": ObjectId("5d6f6d25e079b9fb7d858236"),
"first_name": "Aaron",
"is_finished": false,
"last_name": "Potter",
"user_id": ObjectId("5d6f6d25e079b9fb7d858236")
},
{
"_id": ObjectId("5d8b522d0cf2579e27bc8ce3"),
"first_name": "Harry",
"is_finished": true,
"last_name": "Paul",
"user_id": ObjectId("5d8b522d0cf2579e27bc8ce3")
}
]
}
]
Note : as proposed by #Valijon, you can add a $project stage if you need to slighty re-arrange from here.

It's Work For You.
db_task.aggregate([
{
$match: { '_id': ObjectId("5d8b522d0cf2579c57bc8ce0") }
},
{
$unwind: '$users'
},
{
$lookup: {
from: 'db_user',
localField: 'users.user_id',
foreignField: '_id',
as: 'user_info'
}
},
{
$project: {
$users: {
user_id: 1,
is_finished: 1,
user_info:'$user_info',
}
}
},
{
$group: {
_id: '$_id',
users: {
$push: '$users'
},
}
},
])

Almost the same as vishal pankhaniya solution, but we exclude user_info._id from inner document
db.db_task.aggregate([
{
$unwind: "$users"
},
{
$lookup: {
from: "db_user",
localField: "users.user_id",
foreignField: "_id",
as: "user_info"
}
},
{
$project: {
users: {
user_id: 1,
is_finished: 1,
user_info: "$user_info"
}
}
},
{
$group: {
_id: "$_id",
users: {
$push: "$users"
}
}
},
{
$project: {
"users.user_info._id": 0
}
}
])
Result
[
{
"_id": ObjectId("5d8b522d0cf2579c57bc8ce0"),
"users": [
{
"is_finished": false,
"user_id": ObjectId("5d8b522d0cf2579e27bc8ce3"),
"user_info": [
{
"first_name": "Harry",
"last_name": "Paul"
}
]
},
{
"is_finished": false,
"user_id": ObjectId("5d6f6d25e079b9fb7d858236"),
"user_info": [
{
"first_name": "Aaron",
"last_name": "Potter"
}
]
}
]
}
]
MongoPlayground

Related

How to aggregate lookup sub array documents in mongoDB, and projecting the result?

I have 2 Collections
Posts
Users
Posts collection contains comments array which stores { userId, comment } object, along with other information.
users ollection contains user's information.
I want to return the complete result.
Ex:
{
"postId":"xvzeee",
"post": "Good Morning",
"likedBy":[
12342234,
23456534
]
"comments": [
{
"comment": "very good morning",
"userName": "Max"
},
{
"comment": "v. GM",
"userName": "Suraj"
}
]
}
My Approach to achieve the above result is.
db.wall.aggregate([
{ $lookup: { from: 'profiles', localField: 'likedBy', foreignField: 'profileId', as: 'likedBy' } },
{ $lookup: { from: 'profiles', localField: 'comments.commentedBy', foreignField: 'profileId', as: 'commentedUser' } },
{
$project:{
"likedBy.name": 1,
"likedBy.profileId": 1,
createdBy: 1,
createdAt: 1,
updatedAt: 1,
comments : [{
comment: "$comments.comment",
user: "$commentedUser.name"
}],
"commentedUser.name" : 1
}
}
])
The result is coming like below:
{
"_id" : ObjectId("5dfdbb129f644213c413eb18"),
"likedBy" : [
{
"profileId" : "96444206",
"name" : "Vinay3"
},
{
"profileId" : "400586806",
"name" : "Dev"
}
],
"createdBy" : "96444206",
"commentedUser" : [
{
"name" : "Vinay3"
},
{
"name" : "Dev"
}
],
/*Facing problem in comment array*/
"comments" : [
{
"comment" : [
"Super-awesome",
"FAB",
],
"user" : [
"Vinay3",
"Dev"
]
}
]
}
The comment should look like :
[{
...
"comments" : [
{
"comments" : [
{
comment :"Super-awesome",
user: "Vinay3",
profileId: "11111..."
},
{
comment: "FAB",
user: "Dev",
profileId: "2222..."
}
],
}
]
}]
The Posts Collection looks like this :
[{
"_id" : ObjectId("5dfdbb129f699913c413eb18"),
"post":"Good Morning",
"createdBy" : "96444206",
"postId" : "D9644s5h8m",
"likedBy" : [
"96444206",
"40058680"
],
"comments" : [
{
"commentId" : "COM9644",
"commentedBy" : "96444206",
"comment" : "Super-awesome"
},
{
"commentId" : "COM9644",
"commentedBy" : "96444206",
"comment" : "#FAB"
},
{
"commentId" : "COM00587",
"commentedBy" : "400586806",
"comment" : "marvelous"
}
],
"createdAt" : ISODate("2019-12-21T11:56:26.944+05:30"),
"updatedAt" : ISODate("2019-12-21T12:12:35.047+05:30"),
"__v" : 0
}, {...}, {...}]
User Profiles Collection
[{
"_id" : ObjectId("5dd4ff3abe53181160efa446"),
"accountStatus" : "CONFIRMED",
"profileId" : "400586806",
"name" : "Dev",
"email" : "dev#xyz.com",
"createdAt" : ISODate("2019-11-20T14:24:18.692+05:30"),
"updatedAt" : ISODate("2019-12-20T16:58:06.041+05:30"),
"__v" : 0
}, {...}, {...} ]
How to achieve this, any help will much be appreciated.
However, I'm able to give a solution to my problem like below.
If there any better solution than this will much be appreciated.
db.wall.aggregate([
{ $unwind: '$comments'},
{ $lookup: { from: 'profiles', localField: 'comments.commentedBy', foreignField: 'profileId', as: 'comments.user' } },
{ $unwind: '$comments.user' },
{
$group: {
_id:'$_id',
postId: { $first: "$postId"},
likedBy: { $first: "$likedBy"},
createdBy: { $first: '$createdBy'},
comments: { $push : '$comments' },
createdAt: { $first: "$createdAt" },
updatedAt: { $first: "$updatedAt" },
}
},{
$project: {
_id:1,
postId: 1,
"likedBy.name": 1,
"likedBy.profileId": 1,
createdBy: 1,
comments: {
$map : {
input: '$comments',
as: 'com',
in: {
commentId: "$$com.commentId",
comment: "$$com.comment",
name: "$$com.user.name"
}
}
},
createdAt: 1,
updatedAt: 1,
}
}
])
I want to add isActive => true || false field to the comments object.
now the challenge is how to filter only isActive => true comments ?
Here is my approach
db.posts.aggregate([
{
$unwind: '$likedBy',
},
{
$unwind: '$comments',
},
{
$lookup: {
from: 'users',
localField: 'likedBy',
foreignField: 'profileId',
as: 'likedByUser'
},
},
{
$unwind: '$likedByUser'
},
{
$lookup: {
from: 'users',
localField: 'comments.commentedBy',
foreignField: 'profileId',
as: 'commentByUser'
},
},
{
$unwind: '$commentByUser'
},
{
$group: {
"_id": '$postId',
"post": { $first: 'post'} ,
comments: {
$push: {
"commentId": "$comments.commentId",
"commentedBy": "$likedByUser.name",
"comment": "$comments.comment"
}
},
likes: {
$push: {
"likedByName": "$likedByUser.name",
"likeById": "$likedBy"
}
}
}
},
{
$project: {
"_id": 0,
"postId":'$_id',
"post": 1,
"comments": 1,
"likes": 1
}
}
])

MongoDB: How to make two unions to the same collection with matrices

I have user records with posts and posts shared with them, that is, users can share the posts with other users. I need to be able to bring or get only the posts shared with him, using as a reference the id of the user who shared the post and the id of the post.
when I use the user id as a reference, it works, but when I try to combine it with the id of the post it does not get anything, this happens when I try to use only the id of the post to get the shared posts.
This would be the structure of the records
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f74"),
"name" : "name 4",
"posts" : [
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f72"),
"name" : "post 1"
},
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f73"),
"name" : "post 2"
}
],
"postSharedWithMe" : [
{
"user_id" : "5cd4aaedfcf8d8583cf97494",
"post_id" : "5cd4aaedfcf8d8583cf97492"
},
{
"user_id" : "5cd4aaedfcf8d8583cf97494",
"post_id" : "5cd4aaedfcf8d8583cf97493"
}
]
}
and in this way he tried to consult them
db.users.aggregate([
{ "$match": { "_id": ObjectId("5cd573b2bb9ad84f9bba2f74") }},
{ $unwind:"$postSharedWithMe" },
{ $unwind:"$posts" },
{
$lookup:
{
from: "users",
let: {
user_id: { "$toObjectId": "$postSharedWithMe.user_id"},
post_id : { "$toObjectId": "$postSharedWithMe.post_id"}
},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$_id", "$$user_id" ] },
{ $eq: [ "$posts._id", "$$post_id" ] }
]
}
}
},
],
as: "sharedPosts"
}
},
{ $unwind:"$sharedPosts" },
{ "$group": {
"_id": "$_id",
"sharedPosts": { "$push": "$sharedPosts" }
}
}
])
and this is the result:
Fetched 0 record(s) in 0ms
and this is what I expected
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f74"),
"name" : "username",
"posts" : [
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f72"),
"name" : "post 1"
},
{
"_id" : ObjectId("5cd573b2bb9ad84f9bba2f73"),
"name" : "post 2"
}
],
"sharedPosts" : [
{
"_id" : ObjectId("id"),
"name" : "shared post"
},
{
"_id" : ObjectId("id"),
"name" : "shared post"
}
]
}
apparently I needed to go through all the posts first while referring to the shared posts, the result of this was an array, now I just needed to make the $ unwind and compare with $ eq and it worked!
db.users.aggregate([
{ $match: { "_id": ObjectId("5cd573b2bb9ad84f9bba2f74") }},
{ $unwind: "$postSharedWithMe" },
{
$lookup:
{
from: "users",
let: {
user_id: { $toObjectId: "$postSharedWithMe.user_id"},
post_id : { $toObjectId: "$postSharedWithMe.post_id"}
},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$$user_id", "$_id" ] },
{ $in: ["$$post_id", "$posts._id" ] },
]
}
}
},
{ $unwind: "$posts" },
{ $match: { $expr: { $eq: [ "$posts._id", "$$post_id" ] } } },
],
as: "sharedPosts"
}
},
{ $unwind: "$sharedPosts" },
{ $group: {
_id: "$_id",
name: { "$first": "$name" },
posts: { "$first": "$posts" },
sharedPosts: { $push:
"$sharedPosts.posts"
}
}
}
])

Mongodb aggretate apply sort to lookup results, and add field index number

The aggregate was executed.
I got the results using lookup, but I need a sort.
In addition, I want to assign an index to the result value.
CollectionA :
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA"),
ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
],
"name" : "jason"
}
CollectionB :
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02
},
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01
}
Query:
db.getCollection('A').aggregate([
{
$match : { "_id" : ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup : {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{ $sort: { "item.date" : -1 } }
]);
Want Result:
{
"_id" : ObjectId("5a6cf47415621604942386cd"),
"contents" : [
{
"_id" : ObjectId("BBBBBBBBBBBBBBBBBBBBBBBB")
"title" : "a title",
"date" : 2018-01-01,
"index" : 0
},
{
"_id" : ObjectId("AAAAAAAAAAAAAAAAAAAAAAAA")
"title" : "a title",
"date" : 2018-01-02,
"index" : 1
}],
"name" : "jason"
}
The current problem does not apply to the sort.
And I don't know how to designate an index.
Below Aggregation may you. For your desire result.
db.CollectionA.aggregate([
{
$match: { "_id": ObjectId("5a6cf47415621604942386cd") }
},
{
$lookup: {
from: "CollectionB",
let: { contents: "$contents" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$contents"] } }
},
{ $sort: { date: 1 } }
],
as: "contents"
}
},
{
$project: {
contents: {
$map: {
input: { $range: [0, { $size: "$contents" }, 1 ] },
as: "element",
in: {
$mergeObjects: [
{ index: "$$element" },
{ $arrayElemAt: [ "$contents", "$$element" ]}
]
}
}
}
}
}
])
One way to go about it would be to unwind the array, sort it and then group it back
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$unwind: "$item"
},
{
$sort: {
"item.date": -1
}
},
{
$group: {
_id: "$_id",
contents: {
$push: "$item"
}
}
}
])
Another method is, (this is applicable only if the date field corresponds to the document creation date),
db.A.aggregate([
{
$match: {
"_id": ObjectId("5a6cf47415621604942386cd")
}
},
{
$lookup: {
from: "B",
localField: "contents",
foreignField: "_id",
as: "item"
}
},
{
$sort: {
"item": -1
}
}
])
Basically, this sorts on the basis of _id, and since _id is created using the creation date, it should sort accordingly.

How to check $setDifference in two array using mongo-query

UserDetails
{
"_id" : "5c23536f807caa1bec00e79b",
"UID" : "1",
"name" : "A",
},
{
"_id" : "5c23536f807caa1bec00e78b",
"UID" : "2",
"name" : "B",
}
UserProducts
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "100",
"UID" : "1"
},
{
"_id" : "5c23536f807caa1bec00e79c",
"UPID" : "200",
"UID" : "2"
}
Groups
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"200" // UPID
],
}
}
Step 1
I have to take UID from UserDetails check with UserProducts then take UPID from UserProducts
Step 2
we have to check this UPID mapped to Groups collection or not ?.
members.regularStudent we are mapped UPID
Step 3
Suppose UPID not mapped means i want to print the UPID from from UserProducts
I have tried but couldn't complete this, kindly help me out on this.
Expected Output:
["100"]
Note: Expected Output is ["100"] , because UserProducts having UPID 100 & 200 but Groups collection mapped only 200.
My Code
db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
}
) // returns [ "100", "200" ]
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
}
]) // returns ["200"]
Now i want to check $setDifference of both array, so i had added below code but returning error like $userProductUPIDs is not defined
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ $userProductUPIDs , "$members" ]
},
_id : 0
}
}
])
As this is a follow up to one of my previous answers I will try to fix your code. The bottom line is that you need two queries as you can't upgrade your database so the code should look like below:
var queryResult = db.UserDetails.aggregate(
{
$lookup: {
from: "UserProducts",
localField: "UID",
foreignField: "UID",
as: "userProduct"
}
},
{ $unwind: "$userProduct" },
{
"$project": { "_id" : 0, "userProduct.UPID" : 1 }
},
{
$group: {
_id: null,
userProductUPIDs: { $addToSet: "$userProduct.UPID" }
}
});
let userProductUPIDs = queryResult.toArray()[0].userProductUPIDs;
db.Groups.aggregate([
{
$unwind: "$members.regularStudent"
},
{
$group: {
_id: null,
UPIDs: { $addToSet: "$members.regularStudent" }
}
},
{
$project: {
members: {
$setDifference: [ userProductUPIDs , "$UPIDs" ]
},
_id : 0
}
}
]) // should return 100

$lookup and replace array of ids with values

Consider below Mongo DB 3.2 Document,
Clients Collection Document
{
"_id" : "gcJk4eRRo2WCbgWSL",
"services" : [
"2tLX8ALYfRvbgiurZ",
"wfE5MqgHfu9QKtK7d",
"MEZtABSEeskuRivXJ"
]
}
Also, Services Collection Documents
{ "_id" : "2tLX8ALYfRvbgiurZ", "name" : "GSTR 1" }
{ "_id" : "wfE5MqgHfu9QKtK7d", "name" : "GSTR 2" }
{ "_id" : "MEZtABSEeskuRivXJ", "name" : "GSTR 3" }
Now, the values in services array field in Clients is associated to _id of Services Collection.
Below is the code that I am currently executing,
db.getCollection('Clients').aggregate(
[
{ "$unwind" : { path: "$services"}},
{
"$lookup" : { from: "Services", localField: "services", foreignField: "_id", as: "services" }
},
{ "$unwind" : { path: "$services", preserveNullAndEmptyArrays: true }},
{ "$project" : {
_id : 1, services: '$services.name'
}
}
]
);
Output of Above code execution is,
{ "_id" : "gcJk4eRRo2WCbgWSL", "services" : "GSTR 1" }
{ "_id" : "gcJk4eRRo2WCbgWSL", "services" : "GSTR 2" }
{ "_id" : "gcJk4eRRo2WCbgWSL", "services" : "GSTR 3" }
But Expected output is as below,
{
"_id" : "gcJk4eRRo2WCbgWSL",
"services" : "GSTR 1, GSTR 2, GSTR 3"
}
Any help is highly appreciated.
You can add additional $group by your _id with $push to merge your services into one array.
db.Clients.aggregate(
[
{ "$unwind" : { path: "$services"} },
{
"$lookup" : { from: "Services", localField: "services", foreignField: "_id", as: "services" }
},
{ "$unwind" : { path: "$services", preserveNullAndEmptyArrays: true }},
{ "$project" : {
_id : 1, services: '$services.name'
}
},
{
"$group": {
_id: "$_id",
"services": { "$push": "$services"}
}
}
]
);
Here's how you do that - there is no need for any $unwinds as long as you do not care about the item order inside your string.
db.getCollection('Clients').aggregate([
{
"$lookup": {
from: "Services",
localField: "services",
foreignField: "_id",
as: "services"
}
}, {
"$project": {
"_id" : 1,
"services": {
$substr:
[
{
$reduce: { // transform array of strings into concatenated string
input: '$services.name',
initialValue: '',
in: {
$concat: ['$$value', ', ', '$$this']
}
}
},
2, // remove first two characters as they will be ', '
-1
]
}
}
}])
This, however, can potentially return something like the following document (note the order of the entries in the string):
{
"_id" : "gcJk4eRRo2WCbgWSL",
"services" : "GSTR 1, GSTR 3, GSTR 2"
}
If you need the items inside the string to be sorted you can do it this way:
db.getCollection('Clients').aggregate([
{
"$lookup": {
from: "Services",
localField: "services",
foreignField: "_id",
as: "services"
}
}, {
$unwind: "$services" // flatten the services array
}, {
$sort: {
"services.name": 1 // sort all documents by service name
}
}, {
$group: { // group the everything back into the original structure again
"_id": "$_id", // we want one group per document id
"services": {
$push: "$services" // and all its services in an array - this time reliably sorted!
}
}
}, {
"$project": {
"_id": 1,
"services": {
$substr:
[
{
$reduce: { // transform array of strings into concatenated string
input: '$services.name',
initialValue: '',
in: {
$concat: ['$$value', ', ', '$$this']
}
}
},
2, // remove first two characters as they will be ', '
-1
]
}
}
}])
This way you reliably get the entries sorted by name:
{
"_id" : "gcJk4eRRo2WCbgWSL",
"services" : "GSTR 1, GSTR 2, GSTR 3"
}
You can try using concatArrays.
Like what mickl answered, you can do grouping first and project it to concat the array value.
//previous code
{
"$group": {
_id: "$_id",
"services": { "$push": "$services"}
}
},{ $project: {
_id : "$_id",
"services": { $concatArrays: [ "$services" ] } }
}
hope it helps..