MongoDB $lookup don't replace all of the other objects - node.js

I am trying to use the $lookup method to find users for this object. But when I use it, it replaces the other objects. The data that was outputed is this
"notifications": {
"author": [
{
"username": "UnusualAbsurd",
"status": "Michael Ohare",
"createdAt": "2022-03-08T14:02:53.728Z",
"id": "1",
"avatar": "https://avatars.dicebear.com/api/avataaars/UnusualAbsurd.png"
}
]
}
But what I expected was this
"notifications": [
"author": [
{
"username": "UnusualAbsurd",
"status": "Michael Ohare",
"createdAt": "2022-03-08T14:02:53.728Z",
"id": "1",
"avatar": "https://avatars.dicebear.com/api/avataaars/UnusualAbsurd.png"
}
],
"type": "REQUEST",
"value": "1"
]
How do I make it output the expected version?
This is my code right now
const data = await this.notificatioModel.aggregate([
{
$match: { author: id },
},
{
$project: { _id: 0, __v: 0 },
},
{
$lookup: {
from: 'users',
localField: 'notifications.author',
foreignField: 'id',
as: 'notifications.author',
pipeline: [
{
$project: { _id: 0, email: 0, password: 0 },
},
],
},
},
]);
This is my notification document
This is my user document

Related

How to use aggregation with nested document?

I'm new to mongoose, I'm confuse while create the query. Can you help me?
I have a movie document like this :
"movies": [
{
"id": "635611395a71beb6c5bf0b4d",
"title": "Mission: Impossible - Fallout",
"createdAt": "2022-10-24T04:14:54.445Z",
"cast": [
{
"actor": "635350c581d5b60383f0c4be",
"roleAs": "Ethan Hunt",
"leadActor": true,
"_id": "635658c18b9e50facd7f1fd1"
},
{
"actor": "63560bf55a71beb6c5bf0b1f",
"roleAs": "Ilsa Faust",
"leadActor": false,
"_id": "635658c18b9e50facd7f1fd2"
}
],
"poster": {
"url": "https://res.cloudinary.com/debfn35m1/image/upload/v1666603204/vczxb7lgbonjn8ydsyep.jpg",
"public_id": "vczxb7lgbonjn8ydsyep",
"responsive": [
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_640/v1666603204/vczxb7lgbonjn8ydsyep.jpg",
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_50/v1666603204/vczxb7lgbonjn8ydsyep.jpg"
]
},
"reviews": {
"ratingAvg": "5.0",
"reviewCount": 2
}
},
{
"id": "635614855a71beb6c5bf0bdc",
"title": "Spider-Man: No Way Home",
"createdAt": "2022-10-24T04:28:58.286Z",
"cast": [
{
"actor": "635350a881d5b60383f0c4b8",
"roleAs": "Peter Parker",
"leadActor": true,
"_id": "636a2d6520cf4cf14a11ef95"
},
{
"actor": "635611eb5a71beb6c5bf0b99",
"roleAs": "MJ",
"leadActor": false,
"_id": "636a2d6520cf4cf14a11ef96"
}
],
"poster": {
"url": "https://res.cloudinary.com/debfn35m1/image/upload/v1667902823/tajzr9hpulvzqoytgpxu.jpg",
"public_id": "tajzr9hpulvzqoytgpxu",
"responsive": [
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_640/v1667902823/tajzr9hpulvzqoytgpxu.jpg",
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_470/v1667902823/tajzr9hpulvzqoytgpxu.jpg",
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_50/v1667902823/tajzr9hpulvzqoytgpxu.jpg"
]
},
"reviews": {
"ratingAvg": "8.0",
"reviewCount": 2
}
},
This is my desired result:
{
"id": "635611395a71beb6c5bf0b4d",
"title": "Mission: Impossible - Fallout",
"createdAt": "2022-10-24T04:14:54.445Z",
"cast": [
{
"actor": "635350c581d5b60383f0c4be",
"roleAs": "Ethan Hunt",
"leadActor": true,
"_id": "635658c18b9e50facd7f1fd1"
},
{
"actor": "63560bf55a71beb6c5bf0b1f",
"roleAs": "Ilsa Faust",
"leadActor": false,
"_id": "635658c18b9e50facd7f1fd2"
}
],
"poster": {
"url": "https://res.cloudinary.com/debfn35m1/image/upload/v1666603204/vczxb7lgbonjn8ydsyep.jpg",
"public_id": "vczxb7lgbonjn8ydsyep",
"responsive": [
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_640/v1666603204/vczxb7lgbonjn8ydsyep.jpg",
"https://res.cloudinary.com/debfn35m1/image/upload/c_scale,w_50/v1666603204/vczxb7lgbonjn8ydsyep.jpg"
]
},
"reviews": {
"ratingAvg": "5.0",
"reviewCount": 2
}
},
Im trying to write a filter function to get all movie that has Tom Cruise with actorId as a parameter but cannot get the result.
Can someone please help me with this ?
Since your actor id is inside a cast array with key actor you must specify your key while match, check the updated code below
exports.actorAggregation = (actorId) => {
return [
{
$lookup: {
from: "Movie",
localField: "cast",
foreignField: "_id",
as: "actorFilter",
},
},
{ $unwind: "$cast" },
{
$match: {
"cast.actor._id": { $in: [actorId] }, // or "cast.actor": { $in: [actorId] },
status: { $eq: "public" },
},
},
{
$project: {
title: 1,
poster: "$poster.url",
responsivePosters: "$poster.responsive",
createdAt: "$createdAt",
},
},
];
};

Mongodb aggregate distinct with unique and sort

I have a Ranks collection with documents which looks like this:
[
{
"_id": "1",
"url": "ex1.com",
"keyword": "k1",
"rank": 19,
"createdAt": "2021-06-02",
"user": "616c542660d23fc17469b47e"
},
{
"_id": "2",
"url": "ex1.com",
"keyword": "k1",
"rank": 14,
"createdAt": "2021-06-01",
"user": "616c542660d23fc17469b47e"
},
{
"_id": "3",
"url": "ex1.com",
"keyword": "k2",
"rank": 8,
"createdAt": "2021-05-01",
"user": "616c542660d23fc17469b47e"
},
{
"_id": "4",
"url": "ex2.com",
"keyword": "k3",
"rank": 4,
"createdAt": "2021-05-01",
"user": "616c542660d23fc17469b47e"
}
]
users collection with documents which looks like this:
[
{
_id: "616c542660d23fc17469b47e",
email: "some#email.com"
}
]
I'm trying to run an aggregation which will return each user object + user's data array that grouped by url, each url object has keywords array that includes unique and last (by date) rank keyword
This is what I tried but the query returns all url's keywords, how can i make it return unique and last (by createdAt date) keywords
Rank.aggregate([
{
$match: {}
},
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: '_id',
as: 'user'
}
},
{
$project: {
user: {
$arrayElemAt: ['$user', 0]
},
url: '$url',
keyword: '$keyword',
rank: '$rank',
createdAt: '$createdAt',
}
},
{
$sort: {
createdAt: -1
}
},
{
$group: {
_id: '$user._id',
user: {
$first: '$user'
},
data: {
$push: {
id: '$_id',
url: '$url',
keyword: '$keyword',
rank: '$rank',
createdAt: '$createdAt',
}
}
}
}
])
Expected output:
[{
user: {
_id: "616c542660d23fc17469b47e",
email: "some#email.com"
},
data: [
{
url: "ex1.com",
keywords: [
{
keyword: "k1",
rank: 19,
createdAt: "2021-06-02",
},
{
keyword: "k2",
rank: 8,
createdAt: "2021-05-01"
},
]
},
{
url: "ex2.com",
keywords: [
{
keyword: "k3",
rank: 4,
createdAt: "2021-05-01"
},
]
}
]
}]
Here it is the solution that I came out with. Playground
Full explanation:
We group by "$url","$user" and "$keyword" to get the unique combinations of this fields. AT this point waht we want is only the unique keywords, but we have to use the user and url fields, becouse we would groupBy those later too.Because we order them by createdAt, if we get the first document it will be the last one created.
{
"$sort": {
"createdAt": 1
}
},
{
"$group": {
"_id": [
"$url",
"$user",
"$keyword"
],
"keywords": {
$first: "$$ROOT"
}
}
},
Then we will format this keyword information a bit to group it by url. This step will give us the keywords per URL.
{
"$project": {
"url": "$keywords.url",
"user": "$keywords.user",
"keywords": "$keywords",
"_id": 0
}
},
{
"$group": {
"_id": [
"$user",
"$url"
],
"data": {
$push: "$$ROOT"
}
}
},
Finally we will group the URLs by user. Notice that we have grouped by URL and by user in each groupBy in order to not lose those fields.
{
"$project": {
"url": {
$first: "$data.keywords.url"
},
"user": {
$first: "$data.keywords.user"
},
"keywords": "$data.keywords",
"_id": 0
}
},
{
"$group": {
"_id": "$user",
"data": {
$push: "$$ROOT"
}
}
},
At this step we have almost all the information we needed grouped together. We would perform a lookUp to get the email from the Users collection and do the final mapping to remove some redundant data.
{
$lookup: {
from: "users",
localField: "_id",
foreignField: "_id",
as: "user"
}
},
{
"$unwind": "$user"
},
{
"$project": {
"_id": 0,
"data.user": 0,
"data.keywords._id": 0,
"data.keywords.url": 0,
"data.keywords.user": 0
}
},

Get true or false ( only value not array or object ) from mongodb lookup aggregation

I am trying to make a function that returns if a user is a favorite or not.
I am using MongoDB aggregate on User model with lookout with favourites model ( in which all favourite user has saved ) with pipeline and $project
here is the MongoDB aggregate function query
await User.aggregate([
{
$match: {
_id: ObjectId(_id),
},
},
{
$lookup: {
from: 'userportfolios',
localField: '_id',
foreignField: 'user',
as: 'user_portfolios',
},
},
{
$lookup: {
from: 'favourites',
pipeline: [
{
$match: {
user: ObjectId(data.user._id),
favourites: {
$elemMatch: {
$eq: ObjectId(_id),
},
},
},
},
{
$project: {
_id: 0,
is_favourite: {
$cond: [
{ $ifNull: ['$is_favourite', false] },
{ $literal: true },
{ $literal: false },
],
},
},
},
],
as: 'is_favourite',
},
},
]);
and I am getting this result
[
{
"_id": "602b5078e8eb5339e0134351",
"is_active": true,
"is_admin": false,
"is_block": false,
"user_type": "company",
"firstName": "Sagar",
"lastName": "Davara",
"mobileNumber": "*******",
"email": "s*********#gmail.com",
"password": "b7370338e54b1cb051be0dd54eba2f7f",
"verificationCode": "210768",
"createdAt": "2021-02-16T04:56:24.091Z",
"updatedAt": "2021-02-16T04:56:24.091Z",
"user_portfolios": [],
"is_favourite": [
{
"is_favourite": false
}
]
}
]
but I want to get is_favourite: true instead of an array of object
I need this output from mongodb
[
{
"_id": "602b5078e8eb5339e0134351",
"is_active": true,
"is_admin": false,
"is_block": false,
"user_type": "company",
"firstName": "Sagar",
"lastName": "Davara",
"mobileNumber": "6353764283",
"email": "sagardspeed3#gmail.com",
"password": "b7370338e54b1cb051be0dd54eba2f7f",
"verificationCode": "210768",
"createdAt": "2021-02-16T04:56:24.091Z",
"updatedAt": "2021-02-16T04:56:24.091Z",
"user_portfolios": [],
"is_favourite": false
}
]
Just add below stage after $lookup stage:
{
$addFields: {
is_favourite: { $arrayElemAt: ["$is_favourite.is_favourite", 0] }
}
}

Mongodb, mongoose - Sorting by _id and date using aggregate and group

I am trying to sort by the task._id & date in desc order. I am able to sort by task._id but sortibg bydate doesnt work, I tried changing the order in aggregate still no luck. I get the response but just the order by usertasks were added in collection and not by the usertask.date
User(name, address, etc)
Task(name, icon, assignee)
UserTask(User.ObjectId, Task.ObjectId, date)
User Collection:
{
"users": [
{
"name": "Bill",
"phone": "345"
},
{
"name": "Steve",
"phone": "123"
},
{
"name": "Elon",
"phone": "567"
}
]
}
Task collection:
{
"tasks": [
{
"name": "Run 100m",
"icon": "run",
"assignee": "Elon"
},
{
"name": "Walk 1 hour",
"icon": "walk",
"assignee": "Bill"
},
{
"name": "Jog 30 minutes",
"icon": "jog",
"assignee": "Steve"
}
]
}
UserTasks:
{
"_id": "5e72fec..",
"user": "5e72fa4..",
"task": "5e72fbac..",
"date": "2020-03-03T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:37.027Z",
"updatedAt": "2020-03-19T05:10:37.027Z",
"__v": 0
},
{
"_id": "5e72fed3..",
"user": "5e72fa4e..",
"task": "5e72fbac..",
"date": "2020-03-12T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:43.296Z",
"updatedAt": "2020-03-19T05:10:43.296Z",
"__v": 0
},
{
"_id": "5e72fed6..",
"user": "5e72fa..",
"task": "5e72fb..",
"date": "2020-03-15T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:46.057Z",
"updatedAt": "2020-03-19T05:10:46.057Z",
"__v": 0
},
{
"_id": "5e72feda...",
"user": "5e72fa4..",
"task": "5e72fb..",
"date": "2020-03-07T05:10:10.000Z",
"createdAt": "2020-03-19T05:10:50.785Z",
"updatedAt": "2020-03-19T05:10:50.785Z",
"__v": 0
}
This is the Aggregate that needs changing
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
date: "$date"
}
}
}
},
{
$sort: { _id: 1, "userdata.date": -1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));
The response is shown below, please note the usertask.date. it is NOT sorted
{
"_id": "5e...",
"name": "Run 100m",
"icon": "run",
"assignee": "Elon",
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
}
]
}
As you can see the it is not sorted by date - desc order. The result should be like shown below
"userdata": [
{
"name": "Elon",
"date": "2020-03-21T20:02:38.143Z"
},
{
"name": "Steve",
"date": "2020-03-19T20:02:38.000Z"
},
{
"name": "Bill",
"date": "2020-03-11T20:02:38.000Z"
}
]
$sort will use the last object which comes out from the aggregate pipe and theres no field "date" in this object:
{
$group: {
_id: "$matchedTask._id",
name: {$first: "$matchedTask.name"},
icon: {$first: "$matchedTask.icon"},
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
your group has to returned a field named date in order to be able to sort it
{
$group: {
_id: "$matchedTask._id",
name: ....,
date: ....
}
}
{ $sort: {date: -1}}
if the value you want to sort is indide another object you must specify it on sort:
{$sort: {"userdata.date": -1}}
I fixed it, had to use sort two times, now I am able to get the result as I want
Solution provided below
UserTask.aggregate([
{
$lookup: {
from: "tasks",
localField: "task",
foreignField: "_id",
as: "matchedTask"
}
},
{
$unwind: "$matchedTask"
},
{
$lookup: {
from: "users",
localField: "user",
foreignField: "_id",
as: "matchedUser"
}
},
{
$unwind: "$matchedUser"
},
{
$sort: { date: -1 }
},
{
$group: {
_id: "$matchedTask._id",
name: { $first: "$matchedTask.name" },
icon: { $first: "$matchedTask.icon" },
assignee: { $first: "$matchedTask.assignee" },
userdata: {
$push: {
name: "$matchedUser.name",
execDate: "$date"
}
}
}
},
{
$sort: { _id: 1 }
}
])
.exec()
.then(doc => res.status(200).json(doc))
.catch(err => res.status(400).json("Error: " + err));

Mongoose aggregate pipeline lookup and nested objects

I would like to share my problem with you.
So I have a 3 entities that needs to be accessed on my query:
Evaluation:
[
{
_id: 1,
questionary: 1,
subject: 1
},
{
_id: 1,
questionary: 1,
subject: 2
},
]
User
[
{
_id: 1
name: "John Doe",
photo: "photo1.jpg"
},
{
_id: 2
name: "Paul Smith",
photo: "photo2.jpg"
},
]
questionary
[
{
_id: 1,
title: "questionary 1",
"date": "2020-02-08T00:00:00.000Z"
},
{
_id: 2,
title: "questionary 2",
"date": "2020-02-09T00:00:00.000Z"
}
]
So my target is getting a data like this: A list of questionaries inside that, a list of evaluations related to a questionary, and inside a evaluation I need a user object. Like this:
[
{
"_id": "1",
"title": "questionary 1",
"evaluations": [
{
"_id": "1",
"date": "2020-04-05T18:53:46.948Z"
"user": {
_id: 1,
"name": "John Doe",
"photo": "photo1.jpg"
}
},
{
"_id": "2",
"date": "2020-04-06T18:53:46.948Z",
"user": {
_id: 1,
"name": "John Doe",
"photo": "photo1.jpg"
}
}
]
}
]
My query is:
return await Questionary.aggregate([{
$lookup: {
from: "evaluation",
localField: "_id",
foreignField: "questionary",
as: "evaluations",
}
},
{
$lookup: {
from: "user",
localField: "evaluations.user",
foreignField: "_id",
as: "user",
}
},
{
$project: {
_id: 1,
title: 1,
status: 1,
evaluations: {
_id: 1,
date: 1,
user: "$user"
}
},
},
]);
And my result is:
[
{
"_id": "1",
"title": "questionary 1",
"evaluations": [
{
"_id": "1",
"date": "2020-04-05T18:53:46.948Z"
"user": [
{
_id: 1,
"name": "John Doe",
"photo": "photo1.jpg"
},
{
_id: 2,
"name": "Paul Smith",
"photo": "photo2.jpg"
}
]
},
{
"_id": "2",
"date": "2020-04-06T18:53:46.948Z",
"user": [
{
_id: 1,
"name": "John Doe",
"photo": "photo1.jpg"
},
{
_id: 2,
"name": "Paul Smith",
"photo": "photo2.jpg"
}
]
}
]
}
]
The users of my evaluations are merging, but this is not that I want, I just want the internal information of the user inside the evaluation.
Any suggestion?
You should be able to use a nested $lookup by using uncorrelated sub-queries as described at https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/#join-conditions-and-uncorrelated-sub-queries
In your case that would be:
return await Questionary.aggregate([{
$lookup: {
from: "evaluation",
let: {questId: "$_id"},
pipeline: [{
$match: {
$expr: {
$eq: ["$$questId", "$questionary"]
}
},
}, {
$lookup: {
from: "user",
localField: "subject",
foreignField: "_id",
as: "user",
}
}],
as: "evaluations"
}
}
]);

Resources