mongodb (aggregation) - $lookup with the lookup result - node.js

i am new in aggregation framework and have the following problem/question.
My collections looks like this:
users
_id: ObjectId('604caef28aa89e769585f0c4')
baseData: {
firstName: "max"
username: "max_muster"
...
...
}
activitiesFeed
_id: ObjectId('604cb97c99bbd77b54047370')
userID: "604caef28aa89e769585f0c4" // the user id
activity: "604caf718aa89e769585f0c8" // the activity id
viewed: false
activities
_id: ObjectId('604caf718aa89e769585f0c8')
"baseData": {
"userID":"604caef28aa89e769585f0c4",
"type":0,
"title":"Alessandro send you a friend request.",
"description":"",
"actionID":"604caef28aa89e769585f0c4"
},
aggregate function
const response = ActivityFeed.aggregate([
{
$match: { userID: ObjectId(args.user) },
},
{
$lookup: {
from: 'activities',
localField: 'activity',
foreignField: '_id',
as: 'lookup_activities',
},
},
{ $unwind: '$activities' },
{
$lookup: {
from: 'users',
localField: 'lookup_activities.baseData.actionID',
foreignField: '_id',
as: 'lookup_users',
},
},
]);
How i can made a lookup with the first lookup result?
I made a lookup to the activities collection. This collection holds the id for the second lookup baseData.userID. But with this approach i have no result with the second lookup.
The reason why i made a join to the activities collection is that the actionID can hold a userID or a document id from another collection. It depend on the type in the activities collection.
Is it in the aggregation framework possible to made a lookup which depends on the type and of the previous lookup?
Thank you in advance.

All I did is fixing your aggregation. You unwind the wrong field and please make sure all your ID fields are ObjectId. I highly recommend using MongoDB Compass to assist your aggregation if you are new to the aggregation function. They have stage by stage assistance GUI which will really make your life easier.
mongoplayground
db.activitiesFeed.aggregate([
{
$match: {
userID: ObjectId("604caef28aa89e769585f0c4")
},
},
{
$lookup: {
from: "activities",
localField: "activity",
foreignField: "_id",
as: "lookup_activities",
},
},
{
$unwind: "$lookup_activities"
},
{
$lookup: {
from: "users",
localField: "lookup_activities.baseData.actionID",
foreignField: "_id",
as: "lookup_users",
},
},
])
Sample Used
db={
"users": [
{
_id: ObjectId("604caef28aa89e769585f0c4"),
baseData: {
firstName: "max",
username: "max_muster"
}
}
],
"activitiesFeed": [
{
_id: ObjectId("604cb97c99bbd77b54047370"),
userID: ObjectId("604caef28aa89e769585f0c4"),
activity: ObjectId("604caf718aa89e769585f0c8"),
viewed: false
}
],
"activities": [
{
_id: ObjectId("604caf718aa89e769585f0c8"),
"baseData": {
"userID": ObjectId("604caef28aa89e769585f0c4"),
"type": 0,
"title": "Alessandro send you a friend request.",
"description": "",
"actionID": ObjectId("604caef28aa89e769585f0c4")
},
}
]
}

Related

How can I make lookup on in with productId and categoryId

How can make I lookup on in with productId and categoryId.
Below is the sample of JSON
"data": {
"status": 1,
"offerId": "634017ad34c7b3545bd47681",
"offerType": "buyOneGetOne",
"categoryId": [
"62de9b22dcafd44290ab03c1",
"62dfe695ec4d69cfeab0265d"
],
"productId": [
"633170230e88e2d859d15ee6",
"62de9d102b2b72f80eda7585",
"62de9d1e2b2b72f80eda7588"
],
"title": "Buy One Get One Deal ",
"description": "buy one get more",
"amountType": "amount",
"amount": 12341,
"startDate": "2022-10-04T06:43:39.000Z",
"endDate": "2022-10-08T06:43:39.000Z"
}
I'm going to assume that you are using ObjectId as _id in your categories and products collection. If I understood your question your document as the following minimal structure:
_id: '1',
data: {
...
}
So, you are looking for something like:
db.nameOfYourCollectionWithData.aggregate([{
$addFields: {
'data.categoryId': {
$map: {
input: '$data.categoryId',
'in': {
$toObjectId: '$$this'
}
}
},
'data.productId': {
$map: {
input: '$data.productId',
'in': {
$toObjectId: '$$this'
}
}
}
}
}, {
$lookup: {
from: 'categories',
localField: 'data.categoryId',
foreignField: '_id',
as: 'data.categoryId'
}
}, {
$lookup: {
from: 'products',
localField: 'data.productId',
foreignField: '_id',
as: 'data.productId'
}
}])
First, you have to convert your array ids into ObjectId. After do this, you can perform the lookup stages directly. If your document is 'data', just remove any reference to data in the aggregate pipeline.
If your _id in categories and products are string instead of ObjectId, just remove the $addFields stage, because is no longer neccessary.

lookup with add extra field in mongodb

My OBJ
[{
_id:XXXXXXXXXX,
role:admin
},
{
_id:XXXXXXXXXX,
role:superUser
}]
and need results using aggregation how to solve this using aggregation
[{
name:'username'
role:'test'
}
]
I suppose you need the following
let db1 = db.get().collection(`temp1`);
let db2 = db.get().collection(`temp2`);
await db1.aggregate([
{
$lookup: {
from: "temp2",
localField: "_id", // field in the orders collection
foreignField: "_id", // field in the items collection
as: "users"
}
},
{
$replaceRoot: { newRoot: { $mergeObjects: [{ $arrayElemAt: ["$users", 0] }, "$$ROOT"] } }
},
{ $project: { users: 0 } }
]).toArray()

Mongodb lookup aggregation not getting all field values

I have two collections which are of schema like
driver
_id:5f9c1d897ea5e246945cd73a
agent_name:"Ratnabh Kumar Rai"
agent_email:"ra2614#gmail.com"
agent_phone:"70****63331"
and
reviews
_id:5f9d54cb3a3ee10c6829c0a4
order_id:"5f9d40f096e4a506e8684aba"
user_id:"5f9bcb66f7a5bf4be0ad9973"
driver_id:"5f9c1d897ea5e246945cd73a"
rating:3
text:""
so i want to calculate the avg driver rating . I tried to use lookup aggregation so that i can get all details and then later calculate the sum...what i did was
let doc = await db
.collection("drivers")
.aggregate([
{
$project: {
_id: {
$toString: "$_id",
},
},
},
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "driver_id",
as: "driver_info",
},
},
{
$project: {
agent_email: 1,
orderReview: "$driver_info",
},
},
])
.toArray();
But i am getting result as
{
_id: '5f9d63eb8737e82fbc193dd9',
orderReview: [ [Object], [Object], [Object], [Object], [Object] ]
}
which is partially correct as i also need to get details from my localfield collection that is drivers field, as of now you can see i am only getting id of driver in my projection i also did "agent_email:1" but not getting email
You're actually only projecting _id in the first pipeline and hence only _id is passed to further pipelines, If you need email in further pipelines you need to project it too
let doc = await db.collection("drivers").aggregate([
{
$project: {
_id: {
$toString: "$_id",
},
agent_email: "$agent_email"
},
},
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "driver_id",
as: "driver_info",
},
},
{
$project: {
agent_email: 1,
orderReview: "$driver_info",
},
},
])
MongoDB PlayGround : https://mongoplayground.net/p/h7D-tYJ7sLU
[Update]
I realized that you're doing this for getting average and if you need it to be done in a single aggregate query, here it is how you can do it.
Using unwind operator you can flat the reviews array as objects and then group by _id and use the $avg aggregation operator
db.collection("drivers").aggregate([
{
$project: {
_id: {
$toString: "$_id",
},
agent_email: "$agent_email"
},
},
{
$lookup: {
from: "reviews",
localField: "_id",
foreignField: "driver_id",
as: "driver_info",
},
},
// Makes the driver info flat with other information
{
"$unwind": "$driver_info"
},
{
// Now you can group them
$group: {
_id: "$_id",
// Calculates avg on rating field
avg: {
"$avg": "$driver_info.rating"
},
// To make email field appear in next pipeline.
// You can do it for other fields too like Name, etc
agent_email: {
$first: "$agent_email"
}
}
},
{
$project: {
// select the fields you want to display
agent_email: 1,
avg: 1
},
},
])
MonogoDb playground Link

How to do nested pagination on mongoDB aggregation

I have 3 main collections
Blogs{
id,
title
}
Comments{
id,
message,
blog_id
}
Likes{
id,
user_id,
blog_id
}
I want to fetch blogs, their comments, and likes altogether. Comments should come in pagination 5 at a time and blog posts are also paginated so also keep in mind that User might just want more comments to show and not Blog posts. So how to paginate comments without affecting blog's array?
Expected output:
{
_id: "POST_ID"
title: "STRING",
comments: [
{
commentMessage: "",
userName: ""
},
{
commentMessage: "",
userName: ""
}
]
}
Sample Document:
{
"_id": "5f987b80811aa021ecce5566",
"title": "nodejs2",
"comments": [
{
"commentMessage": "this is comment message 2",
"userName": "user2",
"createdTime": 123456789
},
{
"commentMessage": "this is comment message 3",
"userName": "user2",
"createdTime": 124456789
},
{
"commentMessage": "this is comment message 6",
"userName": "user2",
"createdTime": 123456789
}
]
},
I have got the expected output but unable to figure out where would I put $skip and $limit for comment's array.
Here is my sample code, the question is where should I put $skip and $limit to paginate comments
BlogPost.aggregate([
{ $match: { $or: [{ title: req.body.title }, { tags: req.body.tag }] } },
{
$lookup: {
from: PostComment.collection.name,
localField: "_id",
foreignField: "postId",
as: "comments"
},
},
{
$lookup: {
from: PostLike.collection.name,
localField: "_id",
foreignField: "postId",
as: "blog_like"
}
},
{
$lookup: {
from: User.collection.name,
localField: "comments.userId",
foreignField: "_id",
as: "UserDetail",
},
},
{
$unwind: "$UserDetail"
},
{
$project:
// working code, not an issue
},
{ $skip: 5}, // for blogs <=========== if this is wrong, let me know
{ $limit: 5} // for blogs <=========== if this is wrong, let me know
]).exec((err, blogs) => {
if (err) throw err
console.log("blogs: ", blogs)
return res.send(blogs)
})

Join a collection to an existing aggregate query mongodb

I have the following collection structures in my mongodb database.
Orders
[
{
"_id":"order_abcd",
"name":"Order 1"
},
{
"_id":"order_defg",
"name":"Order 2"
}
]
Session
{
"_id":"session_abcd"
"orders": [ ObjectId("order_abcd"), ObjectId("order_defg") ]
}
Transactions
{
"_id":"transaction_abcd"
"id_session" : ObjectId("session_abcd")
}
What I am trying to achieve is a dataset that looks similar to this
[
{
"_id":"order_abcd",
"name":"Order 1",
"transaction":"transaction_abcd"
},
{
"_id":"order_defg",
"name":"Order 2",
"transaction":"transaction_abcd"
}
]
I do not have any input data other than a start date and an end date which will be used to filter orders, the query is mostly for reporting purposes so in effect I am trying to generate a query to fetch all orders between a given time period and attach the transaction id for each order to it.
We can use couple of $lookup (analogous to join with 2 tables in SQL) with $unwind at each stage to finally $project the key-value pair that is desired.
db.Session.aggregate([
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{$project:{
_id:"$order_info._id",
name:"$order_info.name",
transaction:"$transaction_info._id"
}}
]).pretty();
Which gives an output:
{
"_id" : "order_abcd",
"name" : "Order 1",
"transaction" : "transaction_abcd"
},
{
"_id" : "order_defg",
"name" : "Order 2",
"transaction" : "transaction_abcd"
}
The unwind stages are used to explode the lookup and then cherry pick fields at final project stage.
++UPDATE++
Another option that probably can help reduce the 2nd stage lookup records since $match on dates of Orders can be applied to pass on filtered docs for next stage.
db.Session.aggregate([
{
$lookup: {
from: "Orders",
localField: "orders",
foreignField: "_id",
as: "order_info"
}
},
{ $unwind: "$order_info" },
{
$match: {} //filter on "order_info.property" (i:e; date,name,id)
},
{
$lookup: {
from: "Transactions",
localField: "_id",
foreignField: "id_session",
as: "transaction_info"
}
},
{ $unwind: "$transaction_info" },
{
$project: {
_id: "$order_info._id",
name: "$order_info.name",
transaction: "$transaction_info._id"
}
}
]).pretty();

Resources