Aggregate with lookup and contcat Strings in MongoDb - mongodb

I have two collections events & members :
events Schema :
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
events Sample Doc :
"_id" : ObjectId("5e8b0bac041a913bc608d69d")
"members" : [
{
"status" : 4,
"_id" : ObjectId("5e8b0bac041a913bc608d69e"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"date" : ISODate("2020-04-06T10:59:56.997Z")
},
{
"status" : 1,
"_id" : ObjectId("5e8b0bf2041a913bc608d6a3"),
"memberId" : ObjectId("5e7e2f048f80b46d786bfd67"),
"date" : ISODate("2020-04-06T11:01:06.463Z")
}
],
members Schema :
{
firstname : String
photo : String
}
members Sample Doc :
[{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}]
I made a query with aggregate, and lookup to get populated data of members, and I want to concat the photo fields of the members by a string, but I get an error,
How can I do the concat ?
Query :
db.getCollection('events').aggregate([
{ $match: { _id: ObjectId("5e8b0bac041a913bc608d69d")}},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname" : 1,
"Members.photo": 1,
//"Members.photo": {$concat:["http://myurl", "$Members.photo"]},
"Members._id" : 1,
},
}
])
Result without the concat :
{
"_id" : ObjectId("5e8b0bac041a913bc608d69d"),
"Members" : [
{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"photo" : "/users/5e7dbf5b257e6b18a62f2da9/profile/profile-02b13aef6e.png"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"photo" : "/5e7e2f048f80b46d786bfd67/profile/profile-c79f91aa2e.png"
}
]
}
Error :
$concat only supports strings, not array

You can do that simply by adding pipeline to $lookup stage
db.events.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d"),
},
},
{
$lookup: {
from: "members",
let: { memberId: "$members.memberId" },
pipeline: [
{ $match: { $expr: { $in: ["$_id", "$$memberId"] } } },
{
$project: {
firstname: 1,
photo: { $concat: ["http://myurl", "$photo"] }
}
}
],
as: "Members",
}
},
/** Optional */
{$project : {Members: 1}}
]);
Test : MongoDB-Playground

the alternative of using a pipeline in the above answer
we may use project and group
db.events.aggregate([
{
$match: { _id: ObjectId("5e8b0bac041a913bc608d69d") }
},
{
$unwind: '$members' // to spread the members array into a stream of documents
},
{
$lookup: {
from: "members",
localField: "members.memberId",
foreignField: "_id",
as: "member"
}
},
{
$unwind: '$member' // each document will have array of only one member, so do this unwind to convert it to an object
},
{
$project: { // do the project here to be able to use the $concat operator
'member._id': 1,
'member.firstname': 1,
'member.photo': 1,
'member.photo': { $concat: ['http://myurl', '$member.photo'] } // now we can use the $concat as member is an object, then member.photo exists
}
},
{
$group: { // do that grouping stage to gather all the members belong to the same document in one array again
_id: '$_id',
Members: {
$addToSet: '$member'
}
}
}
])

Related

Not able to receive expected result in mongodb between collections

I'm trying to get a product list with logged user wishlist in the product list itself. I'm trying $lookup but not getting the expected result.
Product Document:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash"
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt"
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
Wishlist Document:
wishlist:
[
{
"_id" : ObjectId("608d8af12b7556c445f5cd87"),
"product" : ObjectId("6044351794bee8b6e0fce48f"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : false
},
{
"_id" : ObjectId("608d8bad2b7556c445f5cd88"),
"product" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"user" : ObjectId("601fb31a60e0a024143e6ea3"),
"isLiked" : true
}
]
Expected Result:
Expected output:
[
{
"_id" : ObjectId("6044351794bee8b6e0fce48f"),
"sku" : "P003474",
"name" : "Kitchen Wash",
"isLiked": false
},
{
"_id" : ObjectId("6085584c42ad3c58c5dbc6b7"),
"sku" : "TS0012",
"name" : "T-shirt",
"isLiked": true
},
{
"_id" : ObjectId("608d20a90e629fcc0d3a93e1"),
"sku" : "Apple1101",
"name" : "Green Apple"
}
]
The query I'm trying:
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
localField: "_id",
foreignField: "product",
as: "product"
}
},
{
$unwind:"$product"
}
])
Not able to get the above-declared result
$unwind causing the issues, first problem is it will remove document when product lookup result empty/[] if you don't specify preserveNullAndEmptyArrays : true, see working behaviour in playground
use $lookup with pipeline and match product id and user id condition
i have changed as name to isLiked in $lookup
$addFields to add isLiked parameter it its present, $arrayElemAt to get isLiked element from first element
db.getCollection('products').aggregate([
{
$lookup: {
from: "wishlists",
let: { product: "$_id" },
pipeline: [
{
$match: {
$and: [
{ $expr: { $eq: ["$$product", "$product"] } },
{ user: ObjectId("601fb31a60e0a024143e6ea3") }
]
}
}
],
as: "isLiked"
}
},
{
$addFields: {
isLiked: { $arrayElemAt: ["$isLiked.isLiked", 0] }
}
}
])
Playground

MongoDB get $lookup leftovers

I'm trying to implement a query that will merge two collections into one and this can be done using $lookup. What I'm trying to achieve is that if the localField doesn't match foreignField I wanted that record to be included as well. Is this possible or am I missing something? Below is what I've tried so far which is a basic $lookup aggregation:
const billPayments = await BillPayment.aggregate([
{
$match: {
isActive: true,
},
},
{
$lookup: {
from: PackageList.collection.name,
localField: "serviceNumber",
foreignField: "serviceNumber",
as: "package",
},
},
{ $unwind: "$package" },
{
$project: {
serviceNumber: 1,
amount: 1,
isCaution: 1,
paymentDate: 1,
remark: 1,
package: "$package",
id: "$_id",
},
},
]);
res.json(billPayments);
The current result of billPayments is only for those BillPayment data which serviceNumber exists in both collections. But I wanted those records which don't exist in PackageList to be included as well. How can I achieve that?
I'm pretty sure I'm missing something here. Any help is appreciated.
#Edit for expected output
"billPayments": [
{
"amount": 299.25,
"id": "604f5bbd6bf57e1f9c58203f",
"isActive": true,
"isCaution": false,
"packageService": {
"_id": "5fc4a5e15ce5bf227451f4b8",
"isActive": true,
"serviceType": "Post_Paid",
"moneyLimit": 3000
},
"employee": {
// Employee data from Employee collection to be included
}
"paymentDate": "2021-03-14T21:00:00.000Z",
"remark": "",
"serviceNumber": "0935998681"
},
{
"amount": 299.25,
"id": "604f5bbd6bf57e1f9c58203f",
"isActive": true,
"packageService": {
// If there is no match for serviceNumber
},
"paymentDate": "2021-03-14T21:00:00.000Z",
"remark": "",
"serviceNumber": "0735448621"
}
]
Edit for additional $lookup query:
I have got a second level $lookup to get additional information from another collection. Which is like below:
{
$lookup: {
from: PackageList.collection.name,
localField: "serviceNumber",
foreignField: "serviceNumber",
as: "package",
},
},
{ $unwind: "$package" },
{
$lookup: {
from: Employee.collection.name,
localField: "package.employee",
foreignField: "_id",
as: "employee",
},
},
As #DheemanthBhat suggested in his answer, I've tried to remove unwind from Package and can easily get an empty list. But if I tried to $lookup on Package to get the employee information, it throws MongoError since it cannot lookup on an empty object.
Is there any method to skip those empty packages from a second level $lookup? Or how can I use $ifNull in this case to skip $unwind from empty results?
So when you perform $lookup without $unwind all the details from BillPayment collection matched or unmatched with the PackageList collection will be available. If you $unwind then the documents with empty packageService will be removed. Based on your expected output If you want an empty packageService object then check the below query.
Note: Please verify the collection/model name and field names before using the below code.
const billPayments = await BillPayment.aggregate([
{
$match: { isActive: true }
},
{
$lookup: {
from: "PackageList",
localField: "serviceNumber",
foreignField: "serviceNumber",
as: "packageService"
}
},
{
$project: {
serviceNumber: 1,
amount: 1,
isCaution: 1,
paymentDate: 1,
remark: 1,
package: "$package",
id: "$_id",
packageService: {
$ifNull: [{ $arrayElemAt: ["$packageService", 0] }, {}]
}
}
}
]);
Output:
/* 1 createdAt:3/15/2021, 6:36:05 PM*/
{
"_id" : ObjectId("604f5bbd6bf57e1f9c58203f"),
"isCaution" : false,
"amount" : 299.25,
"paymentDate" : ISODate("2021-03-15T02:30:00.000+05:30"),
"serviceNumber" : "0935998681",
"id" : ObjectId("604f5bbd6bf57e1f9c58203f"),
"packageService" : {
"_id" : ObjectId("5fc4a5e15ce5bf227451f4b8"),
"serviceNumber" : "0935998681",
"isActive" : true,
"serviceType" : "Post_Paid",
"moneyLimit" : 3000
}
},
/* 2 createdAt:3/15/2021, 6:36:05 PM*/
{
"_id" : ObjectId("604f5bbd6bf57e1f9c582040"),
"isCaution" : false,
"amount" : 300,
"paymentDate" : ISODate("2021-03-16T02:30:00.000+05:30"),
"serviceNumber" : "0735448621",
"id" : ObjectId("604f5bbd6bf57e1f9c582040"),
"packageService" : {
}
}
Test data:
BillPayment collection
{
"_id" : ObjectId("604f5bbd6bf57e1f9c58203f"),
"isActive" : true,
"isCaution" : false,
"amount" : 299.25,
"paymentDate" : ISODate("2021-03-15T02:30:00.000+05:30"),
"serviceNumber" : "0935998681"
},
{
"_id" : ObjectId("604f5bbd6bf57e1f9c582040"),
"isActive" : true,
"isCaution" : false,
"amount" : 300,
"paymentDate" : ISODate("2021-03-16T02:30:00.000+05:30"),
"serviceNumber" : "0735448621"
}
PackageList collection
{
"_id" : ObjectId("5fc4a5e15ce5bf227451f4b8"),
"serviceNumber" : "0935998681",
"isActive" : true,
"serviceType" : "Post_Paid",
"moneyLimit" : 3000
}

Do count with condition inside mongDB aggregation's function

I have 2 collections inside my MongoDB:
Order:
{
"date" : ISODate("2020-07-30T22:00:00.000Z"),
"item" : "banana",
"price": "4$",
"order_type" : "INTERNATIONAL", // There are 2 order types: INTERNATIONAL and MAINLAND
"user" : { // I use a dbref to the User collection
"$ref" : "user",
"$id" : "user_0"
}
}
User:
{
"_id": "user_0"
"login" : "user1",
"password" : "$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG",
"first_name" : "Henry",
"last_name" : "Windsor",
"email" : "hw#gmail.com",
}
Each order contains a DB reference to the corresponding user who made it. This is my mongo code to calculate the total number of orders that each user makes.
db.getCollection('order').aggregate([
{$group: {
_id: '$user',
totalNbOfOrders: {$sum: 1}
}},
{$addFields: {foreign_key: {$objectToArray: "$_id"}}},
{$lookup: {
from: 'user',
localField: 'foreign_key.1.v',
foreignField: '_id',
as: 'userInfo'
}
},
{ $unwind: '$userInfo'},
{ $project: {
'_id': 0,
'first_name': '$userInfo.first_name',
'last_name': '$userInfo.last_name',
'totalNbOfOrders': '$totalNbOfOrders'
}
}
])
And the result is:
/* 1 */
{
"first_name" : "John",
"last_name" : "Kennedy",
"totalNbOfOrders" : 2.0
}
/* 2 */
{
"first_name" : "Peter",
"last_name" : "Parker",
"totalNbOfOrders" : 4.0
}
/* 3 */
{
"first_name" : "Bruce",
"last_name" : "Banner",
"totalNbOfOrders" : 2.0
}
Now, what I also want to calculate is the number of international orders (and eventually of mainland orders) that each user made to have something like this:
{
"first_name" : "Tony",
"last_name" : "Stark",
"totalNbOfOrders" : 10.0,
"totalNbOfInternationalOrders": 4.0
"totalNbOfMainlandOrders": 6.0
}
I haven't figured out how to write the code.
I tried to use "$accumulator" operator (new feature in version 4.4 of MongoDB) inside "$group" but I used MongoDB 4.2.7, I have to use operators from older versions to accomplish this. Does anybody know how to solve this problem?
You can do it inside $group, using $cond and $eq,
{
$group: {
... // skipped
// INTERNATIONAL COUNT
totalNbOfInternationalOrders: {
$sum: {
$cond: {
if: {
$eq: ["$order_type", "INTERNATIONAL"]
},
then: 1,
else: 0
}
}
},
// MAINLAND COUNT
totalNbOfMainlandOrders: {
$sum: {
$cond: {
if: {
$eq: ["$order_type", "MAINLAND"]
},
then: 1,
else: 0
}
}
}
}
},
set in $project
{
$project: {
... // skipped
"totalNbOfInternationalOrders": "$totalNbOfInternationalOrders",
"totalNbOfMainlandOrders": "$totalNbOfMainlandOrders"
}
}
Playground: https://mongoplayground.net/p/_IeVcSFt_nY

How to combined two output in lambda function using mongodb?

I have two collection 1) profile ,2) posts find the below pic for your reference. user_prfoile collection
user_posts collection
In the lambda function, when i passed the userid, then we will get the userid revlevant data display. but i need user details in feelings array.
I tried with the below code, but i get the empty output
def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=1))
Userid = event['userid']
uid = ObjectId(Userid)
dispost = list( db.user_posts.aggregate([{ "$match" : { "userid" : uid } },
{ "$unwind" : "$user_profile" },
{"$lookup":
{
"from" : 'user_profile',
"localField" : 'feelings.userid',
"foreignField" : '_id',
"as" : 'user_details'
}
},
{ "$addFields" : { "user_details.feelings.username" : "$user_profile.username", "user_details.feelings.photo" : "$user_profile.photo"}},
{ "$group" : {
"_id" : "$_id",
"user_profile" : {
"$push" : {
"$arrayElemAt" : ["$user_details", 0]
}}}},
{ "$project" : { "_id" : "$_id", "userid" : 1, "type" : 1, "feelings" : 1 }}
]))
disair = json.dumps(dispost, default=json_util.default)
return json.loads(disair)
I get an empty output.
I need output like this below.
_id :
userid :
type :
location :
feelings :
[ {userid : objectid(""),
username : "nipun",
photo : " "},
{userid : objectid(""),
username : "ramesh",
photo : " "}
]
in feelings array , i need user details from user_profile collection based on userid in feelings array.
Ok, there are couple of issues with that aggregation query, keeping most of it as is, I've modified it to work, as of now please try this and make any changes needed :
db.getCollection('user_posts').aggregate([{ "$match": { "userid": uid } },
{ "$unwind": "$feelings" },
{
"$lookup":
{
"from": 'user_profile',
"localField": 'feelings.userid',
"foreignField": '_id',
"as": 'feelings'
}
},
{
"$group": {
"_id": "$_id", userPost: { "$first": "$$CURRENT" }, "feelings": {
"$push": { "$arrayElemAt": ["$feelings", 0] }
}
}
}, { "$project": { userid: '$userPost.userid', type: '$userPost.type', "feelings": 1 } }
])

How to $slice results of foreign collection documents array when mongodb $lookup is used

Here is my code
AbcSchema.aggregate([
{ $match: query },
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
}
])
Right now Im getting this result :
{
_id : "abc1",
abcfield1 : "...",
abcfield2 : "...",
xyzArray : [{_id : "xyz1", place_id : "abc1", xyzfield1 : "..."},
{_id : "xyz2", place_id : "abc1", xyzfield1 : "..."},
{_id : "xyz3", place_id : "abc1", xyzfield1 : "..."},
...] //all matching results
}
So now lets say I want only 2 documents in xyzArray, then how can I achieve that?
My requirement is to get limit the 'xyzArray' length to 'n' .
Here is the dynamic query. You can change the number inside the $slice to get the required number of array elements.
db.AbcSchema.aggregate([
{ $match: {_id : "abc1"} },
{
$unwind: "$_id"
},
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
},
{ $project: { abcfield1 : 1,
abcfield2 : 1,
xyzArrayArray: { $slice: ["$xyzArray", 2] }
}
}
]).pretty();
One possible solution using $project and $arrayElemAt
db.AbcSchema.aggregate([
{ $match: {_id : "abc1"} },
{
$unwind: "$_id"
},
{
$lookup: { from: 'xyz', localField: '_id', foreignField: 'place_id', as: 'xyzArray' }
},
{ $project: { abcfield1 : 1,
abcfield2 : 1,
firstxyzArray: { $arrayElemAt: [ "$xyzArray", 0 ] },
secondxyzArray: { $arrayElemAt: [ "$xyzArray", 1 ] }
}
}
]).pretty();
Sample Output:-
{
"_id" : "abc1",
"abcfield1" : "11",
"abcfield2" : "22",
"firstxyzArray" : {
"_id" : "xyz1",
"place_id" : "abc1",
"xyzfield1" : "xyzf1"
},
"secondxyzArray" : {
"_id" : "xyz2",
"place_id" : "abc1",
"xyzfield1" : "xyzf1"
}
}