Find user is registered to a Event in MongoDb (aggregation) - javascript

I tried to find users who are registered for that event.
So I join multiple collections shown below -
Events.aggregate([
{ $match: { category: "group_event" } },
// collection where events are scheduled
{
$lookup: {
from: "group_events",
let: { eventId: "$eventID" },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ["$_id", "$$eventId"] },
{ $gt: ["$time", new Date()] },
],
},
},
},
// register user collection
{
$lookup: {
from: "register_events",
let: { eventId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$eventId", "$$eventId"] } } },
],
as: "registerUsers",
},
},
],
as: "events",
},
},
{ $unwind: "$events" },
])
and the output is now comingout -
[
{
"_id": "614d6dfd82cb36be231083c9",
"trainerId": "61488dc36b7ccedbc884d20a",
"category": "group_event",
"eventID": "614d6dfc82cb36be231083c7",
"createdAt": "2021-09-24T06:19:41.268Z",
"updatedAt": "2021-09-24T06:19:41.268Z",
"__v": 0,
"events": {
"_id": "614d6dfc82cb36be231083c7",
"groupName": "group name 4",
"category": "sdfsdf",
"time": "2021-09-27T07:44:58.762Z",
"description": "description",
"day": "sunday",
"platform": "zoom",
"notes": "22",
"skills_to_learn": [
"demo"
],
"status": "pending",
"trainerId": "61488dc36b7ccedbc884d20a",
"meetingLink": "https://us05web.zoom.us/j/81660534858?pwd=cGZaODVjdWJUQWNtN243MlNiVUN0UT09",
"type": "group_event",
**isUserRegisted : true / false,**
"createdAt": "2021-09-24T06:19:41.000Z",
"updatedAt": "2021-09-24T06:19:41.000Z",
"__v": 0,
"registerUsers": [
{
"_id": "614ed6b4b8a545acb8517e85",
"userId": "614d59371d11becb8e23f536",
"eventId": "614d6dfc82cb36be231083c7",
"question": "",
"createdAt": "2021-09-25T07:58:44.939Z",
"updatedAt": "2021-09-25T07:58:44.939Z",
"__v": 0
}
]
}
}
]
which is ok for me bu just wanted to add a key: value, heighlited on obove section
isUserRegisted : true / false
i tried with $addFields but can't came up with any solution. Basically I need to retrieve arrays from registerUsers - collection and on the same time match the userId

I was able to figure out this issue.
simply I need to use $project and $filter to get the data if available and at last use $cond to return true or false
{
$project: {
root: "$$ROOT",
userFound: {
$filter: {
input: "$registerUsers",
as: "ac",
cond: {
$eq: ["$$ac.userId", mongoose.Types.ObjectId(userId)],
},
},
},
},
},
{
$project: {
_id: 0,
document: "$$ROOT",
userFound: {
$cond: {
if: { $isArray: "$userFound" },
then: {
$cond: {
if: {
$gt: [{ $size: "$userFound" }, 0],
},
then: true,
else: false,
},
},
else: false,
},
},
},
},
// merging nested object with parents
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$document.root",
{ isUserRegistered: "$userFound" },
],
},
},
},

Related

MongoDB : - Merge object with same key into one

I am trying to merge an object inside an array with the same date but with a different key name for the status key.
I have 2 collections users and canteens
The query I am trying to get the result but am not able to figure out how to merge the object with the same Date
OUTPUT
User.aggregate([
{ $sort: { workerId: 1 } },
{
$lookup: {
from: "canteens",
localField: "_id",
foreignField: "employeeId",
pipeline: [
{
$match: {
Date: {
$gte: new Date(fromDate),
$lte: new Date(toDate),
},
},
},
{
$project: {
Date: 1,
status: 1,
},
},
],
as: "canteens",
},
},
{
$project: {
_id: 1,
workerId: 1,
workerFirstName: 1,
workerSurname: 1,
workerDepartment: 1,
workerDesignation: 1,
locationName: 1,
canteenData: "$canteens",
},
},
]);
[
{
"_id": "60e6fd3616dd663e84a925e2",
"workerFirstName": "Firstaname",
"workerSurname": "lastname",
"workerId": "1",
"locationName": "location",
"workerDesignation": "designation",
"workerDepartment": "department",
"canteenData": [
{
"_id": "63b285b9e92eee614feb7be1",
"status": "LUNCH",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b2db8db10c24487201e0a2",
"status": "DINNER",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b39b247adbeb50bfbe3503",
"status": "BREAK FAST",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b3d248c076184fb07ff2c4",
"status": "LUNCH",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b42b8ccb57a4cb7af34015",
"status": "DINNER",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b4ef71e038498fe6634506",
"status": "BREAK FAST",
"Date": "2023-01-04T00:00:00.000Z"
}
]
},
{
"_id": "60e6fd3616dd663e84a925e2",
"workerFirstName": "Firstaname1",
"workerSurname": "lastname1",
"workerId": "2",
"locationName": "location",
"workerDesignation": "designation",
"workerDepartment": "department",
"canteenData": [
{
"_id": "63b285b9e92eee614feb7be1",
"status": "LUNCH",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b2db8db10c24487201e0a2",
"status": "DINNER",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b39b247adbeb50bfbe3503",
"status": "BREAK FAST",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b3d248c076184fb07ff2c4",
"status": "LUNCH",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b42b8ccb57a4cb7af34015",
"status": "DINNER",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b4ef71e038498fe6634506",
"status": "BREAK FAST",
"Date": "2023-01-04T00:00:00.000Z"
}
]
}
]
The output I am trying to get
[
{
"_id": "60e6fd3616dd663e84a925e2",
"workerFirstName": "Firstanem",
"workerSurname": "lastname",
"workerId": "1",
"locationName": "location",
"workerDesignation": "designation",
"workerDepartment": "department",
"canteenData": [
{
"_id": "63b285b9e92eee614feb7be1",
"status1": "LUNCH",
"status2": "DINNER",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b39b247adbeb50bfbe3503",
"status1": "BREAK FAST",
"status2": "LUNCH",
"status3": "DINNER",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b4ef71e038498fe6634506",
"status1": "BREAK FAST",
"Date": "2023-01-04T00:00:00.000Z"
}
]
},
{
"_id": "60e6fd3616dd663e84a925e2",
"workerFirstName": "Firstanem1",
"workerSurname": "lastname1",
"workerId": "2",
"locationName": "location",
"workerDesignation": "designation",
"workerDepartment": "department",
"canteenData": [
{
"_id": "63b285b9e92eee614feb7be1",
"status1": "LUNCH",
"status2": "DINNER",
"Date": "2023-01-02T00:00:00.000Z"
},
{
"_id": "63b39b247adbeb50bfbe3503",
"status1": "BREAK FAST",
"status2": "LUNCH",
"status3": "DINNER",
"Date": "2023-01-03T00:00:00.000Z"
},
{
"_id": "63b4ef71e038498fe6634506",
"status1": "BREAK FAST",
"Date": "2023-01-04T00:00:00.000Z"
}
]
}
]
One option is to add 2 steps into your $lookup pipeline aggregation:
{$group: {
_id: "$Date",
_idVal: {$first: "$_id"},
data: {$addToSet: "$status"}
}},
{$replaceRoot: {
newRoot: {
$mergeObjects: [
{_id: "$_idVal", Date: "$_id"},
{$arrayToObject: {
$reduce: {
input: "$data",
initialValue: [],
in: {$concatArrays: [
"$$value",
[{k: {$concat: [
"status",
{$toString: {$add: [{$size: "$$value"}, 1]}}
]},
v: "$$this"}]
]}
}
}}
]
}
}}
See how it works on the playground example
It's not easy to create status1, status2, ... variables dynamically + how do we know BREAK FAST should be status1 and not status2.
Alternative solution: We $group inside correlated subqueries and push all status values into an array
db.users.aggregate([
{
"$lookup": {
"from": "canteens",
"localField": "_id",
"foreignField": "employeeId",
pipeline: [
{
// Put your custom filters here
$match: {}
},
{
$group: {
_id: "$Date",
//pick "first" canteens _id
id: {
$first: "$_id"
},
status: {
$push: "$status"
}
}
},
{
$project: {
_id: "$id",
Date: "$_id",
status: 1
}
},
],
as: "canteenData",
}
}
])
MongoPlayground

How to group result of lookup aggregate results

I aggregate users document:
User.aggregate([
{
$match: { _id: req.params.id },
},
{
$lookup: {
from: 'moods',
localField: '_id',
foreignField: 'source.userId',
pipeline: [
{
$lookup: {
from: 'contactrequests',
localField: 'source.userId',
foreignField: 'source.userId',
let: {
// truncate timestamp to start of day
moodsDate: {
$dateTrunc: {
date: '$timestamp',
unit: 'day',
},
},
},
pipeline: [
{
$match: {
$expr: {
$eq: [
'$$moodsDate',
{
// truncate timestamp to start of day
$dateTrunc: {
date: '$timestamp',
unit: 'day',
},
},
],
},
},
},
],
as: 'contactRequests',
},
},
],
as: 'calendar',
},
},
]).exec()
There is final document what I get
{
"_id": "P4SpYVd1KjBaF4SKyVw0E",
"lastName": "Doe",
"login": "User-01",
"name": "John"
"calendar": [
{
"_id": "62a351e33859aaf975c63323",
"source": {
"userId": "P4SpYVd1KjBaF4SKyVw0E",
"deviceId": "Pacjent-141214"
},
"timestamp": "2022-06-07T12:44:13.333Z",
"mood": "good",
"contactRequests": []
},
{
"_id": "62a351f43859aaf975c63327",
"source": {
"userId": "P4SpYVd1KjBaF4SKyVw0E",
"deviceId": "Pacjent-141214"
},
"timestamp": "2022-06-09T12:44:13.333Z",
"mood": "middle",
"contactRequests": [
{
"timestamp": "2022-06-09T12:44:13.333Z",
"source": {
"deviceId": "Pacjent-141214",
"userId": "P4SpYVd1KjBaF4SKyVw0E"
},
"resolve": false,
"_id": "62a351ff3859aaf975c63329",
},
]
}
]
},
This is what I would to get. This is more clean and readable.
{
"_id": "P4SpYVd1KjBaF4SKyVw0E",
"login": "User-01",
"name": "John",
"lastName": "Doe",
"calendar": [
{
"timestamp": "2022-06-11T12:44:13.333Z"
"mood": {
"source": {
"userId": "P4SpYVd1KjBaF4SKyVw0E",
},
"timestamp": "2022-06-11T12:44:13.333Z",
"mood": "bad",
"_id": "62a352b83859aaf975c6332d",
},
"contactRequest": [
{
"timestamp": "2022-06-11T15:25:13.333Z",
"source" : {
"userId":"P4SpYVd1KjBaF4SKyVw0E"
},
"resolve": true,
"_id": "62a351ff3859aaf975c63329"
},
{
"timestamp": "2022-06-11T18:23:13.333Z",
"source" : {
"userId":"P4SpYVd1KjBaF4SKyVw0E"
},
"resolve": false,
"_id": "62a351ff3859aaf975c63329"
},
]
}
}
]
}
To achive that I've used $group parameter, but at some point I have to declare which field should be fetch to result document and I have problem with contatRequest fields.
{
$group: {
_id: {
$dateToString: {
format: '%Y-%m-%d',
date: '$timestamp',
},
},
mood: {
$push: {
_id: '$_id',
source: '$source',
type: '$mood',
timestamp: '$timestamp',
},
},
contactRequest: {
$push: {
_id: '$contactRequest._id',
source: '$contactRequest.source',
resolve: '$contactRequest.resolve',
timestamp: '$contactRequest.timestamp',
},
},
},
},
{
$project: {
_id: 0,
timestamp: '$_id',
mood: 1,
contactRequest: 1,
},
},
Sample database/collections/aggregation pipeline at mongoplayground.net.

MongoDB get max date inside double nested array

Assume I have the following document:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "b0d14381-ce75-49aa-a66a-c36ae20b72a8",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
},
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-01T06:00:00.000Z",
"status": "routed"
},
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
I want to get results where inquiries.routeHistory.routeDate is equal to the $max routeDate value in routeHistory. I would expect my results to look like the following:
[
{
"callId": "17dac51e-125e-499e-9064-f20bd3b1a9d8",
"caller": {
"firstName": "Test",
"lastName": "Testing",
"phoneNumber": "1231231234"
},
"inquiries": [
{
"inquiryId": "b0d14381-ce75-49aa-a66a-c36ae20b72a8",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
},
{
"inquiryId": "9d743be9-7613-46d7-8f9b-a04b4b899b56",
"routeHistory": [
{
"assignedUserId": "cfa0ffe9-c77d-4eec-87d7-4430f7772e81",
"routeDate": "2020-01-03T06:00:00.000Z",
"status": "ended"
}
]
}
]
}
]
Is there a clean way to do this in a single aggregate, so that additional $match criteria can be applied? One caveat is that I can only use operators supported by DocumentDB: https://docs.aws.amazon.com/documentdb/latest/developerguide/mongo-apis.html
I have tried the following code, but to no avail:
{
$addFields: {
maxDate: {
$max: '$inquiries.routeHistory.routeDate',
},
},
},
{
$addFields: {
routeHistory: [
{
$arrayElemAt: [
{
$filter: {
input: '$inquiries.routeHistory',
cond: {
$eq: ['$maxDate', '$$this.routeDate'
],
},
},
},
0,
],
},
],
},
}
You have to use $map to scan outer array and to $filter to compare inner array's elements against $max date:
db.collection.aggregate([
{
$addFields: {
inquiries: {
$map: {
input: "$inquiries",
as: "inquiry",
in: {
inquiryId: "$$inquiry.inquiryId",
routeHistory: {
$filter: {
input: "$$inquiry.routeHistory",
cond: {
$eq: [ { $max: "$$inquiry.routeHistory.routeDate" }, "$$this.routeDate" ]
}
}
}
}
}
}
}
}
])
Mongo Playground
EDIT: by looking at your link I've noticed that $map is not supported, you can use below combination as a workaround:
db.collection.aggregate([
{
$unwind: "$inquiries"
},
{
$addFields: {
"inquiries.routeHistory": {
$filter: {
input: "$inquiries.routeHistory",
cond: {
$eq: [ { $max: "$inquiries.routeHistory.routeDate" }, "$$this.routeDate" ]
}
}
}
}
},
{
$group: {
_id: "$_id",
callId: { $first: "$callId" },
caller: { $first: "$caller" },
inquiries: { $push: "$inquiries" }
}
}
])
Mongo Playground (2)

How to change field from subdocument a parent field in Mongoose

I am trying to export Mongo data to XLSX which requires all the data to be in the parent map but currently I have data in this format:
[
{
"_id": "eatete",
"competition": {
"_id": "eatete"
"name": "Some competition name"
},
"members": [
{
"_id": "eatete",
"name": "Saad"
},
{
"_id": "eatete",
"name": "Saad2"
}
],
"leader": {
"name": "Saad",
"institute": {
"_id": "eatete",
"name": "Some institute name"
}
},
}
]
Ideally, the data should be:
[
{
"_id": "eatete",
"competition": "Some competition name"
"member0name": "Saad",
"member1name": "Saad2",
"leadername": "Saad",
"institute": "Some institute name"
}
]
So basically what I want is to refer the data of fields of subdocuments as if those were part of parent document, like competitions = competitions.name.
Can you please help me how can I do so using Mongoose.
Thanks
With some more aggregation trick
db.collection.aggregate([
{ "$unwind": { "path": "$members", "includeArrayIndex": "i" }},
{ "$group": {
"_id": "$_id",
"competition": { "$first": "$competition.name" },
"leadername": { "$first": "$leader.name" },
"institute": { "$first": "$leader.institute.name" },
"data": {
"$push": {
"k": { "$concat": ["members", { "$toLower": "$i" }, "name"] },
"v": "$members.name"
}
}
}},
{ "$replaceRoot": {
"newRoot": {
"$mergeObjects": ["$$ROOT", { "$arrayToObject": "$data" }]
}
}},
{ "$project": { "data": 0 }}
])
You can try below aggregation on your Model:
let resultt = await Model.aggregate([
{
$project: {
_id: 1,
competition: "$competition.name",
leadername: "$leader.name",
institute: "$leader.institute.name",
members: {
$map: {
input: { $range: [ 0, { $size: "$members" } ] },
in: {
k: { $concat: [ "member", { $toString: "$$this" }, "name" ] },
v: {
$let: {
vars: { current: { $arrayElemAt: [ "$members", "$$this" ] } },
in: "$$current.name"
}
}
}
}
}
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [ "$$ROOT", { $arrayToObject: "$members" } ]
}
}
},
{
$project: {
members: 0
}
}
])
Since you need to dynamically evaluate your keys based on indexes you can use $map with $range to map a list of indexes into keys of a new object. Then you can use $arrayToObject to get an object from those keys and $mergeObjects with $replaceRoot to flatten this object structure.

$lookup with deeply nested object

I am new to MongoDB and currently working on a recipe App for school which suggests diet plans. Therefore I need to join the "meal" ObjectId in the diet plan of the user (collection "Users") with the ObjectIds in the collections "Meals".
Afterwards I need to join an "ingredient" ObjectID in the "Meals" collection with the ID of the "ingredient" in the "Ingredients" collection. The problem is, that the "ingredient" ObjectID in collection "Meals" is situated in an Object with another integer variable "amount". This object is nested in an array called "ingredients" with many objects such as the one just described.
Below is my Structure:
Users
{
"_id": ObjectId("5b28cab902f28e18b863bd36"),
"username: "testUser1",
"password": "$2a$08$KjddpaSQPjp6aF/gseOhVeddYdqWJCJ4DpFwxfNgsk81G.0TOtN5i",
"dietPlans": Object
{
"dietPlanCurrent":Object
{
"monday":Object
{
"breakfast":Object
{
"meal": ObjectId("5b2b9a8bbda339352cc39ec4")
},
…
},
…
},
…
},
}
Meals
{
"_id" : ObjectId("5b2b9a8bbda339352cc39ec4"),
"name": "Gulasch-breakfast",
"cuisine": "International",
"ingredients":[
{
"ingredient": ObjectId("5b1ec0f939b55efcd4e28a2d"),
"amount": 20
},
{
"ingredient": ObjectId("5b1ec42474fc1f58d84264d4"),
"amount": 20
},
{
"ingredient": ObjectId("5b1ec42474fc1f58d84264d5"),
"amount": 20
},
…
],
"comments": [
…
]
}
Ingredients
{
{
"_id": ObjectId("5b1ec0f939b55efcd4e28a2d"),
"name": "Walnut",
"calories": 654
…
}
{
"_id": ObjectId("5b1ec0f939b55efcd4e28a3d"),
"name": "Apple",
"calories": 123
…
}
…
}
What I am trying to get is:
{
"_id": ObjectId("5b28cab902f28e18b863bd36"),
"username: "testUser1",
"password": "$2a$08$KjddpaSQPjp6aF/gseOhVeddYdqWJCJ4DpFwxfNgsk81G.0TOtN5i",
"dietPlans": Object
{
"dietPlanCurrent":Object
{
"Monday":Object
{
"breakfast":Object
{
"meal": ObjectId("5b2b9a8bbda339352cc39ec4")
"matchedIngredients": [
{
"_id": ObjectId("5b1ec0f939b55efcd4e28a2d"),
"name": "Walnut",
"calories": 654
…
}
…
]
},
…
},
…
},
…
},
}
My approach which is not working (only returning empty matchedIngredients Array)
{
$match: {
'_id': mongoose.Types.ObjectId(req.params.userId)
}
},
{
$lookup: {
from: 'meals',
localField: 'dietPlans.dietPlanCurrent.monday.breakfast.meal',
foreignField: '_id',
as: "dietPlans.dietPlanCurrent.monday.breakfast.mealObject"
}
},
{
$unwind: {
path: "$dietPlans.dietPlanCurrent.monday.breakfast.mealObject",
preserveNullAndEmptyArrays: true
}
},
{
$unwind: {
path: "$dietPlans.dietPlanCurrent.monday.breakfast.mealObject.ingredients",
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'ingredients',
localField: 'dietPlans.dietPlanCurrent.monday.breakfast.mealObject.ingredients.ingredient',
foreignField: '_id',
as: "dietPlans.dietPlanCurrent.monday.breakfast.matchedIngredients"
}
}
Help is very much appreciated. I already checked out this approach, but it somehow didn't work:
Approach that didn't work for me
Thank you very much!
What you are trying to do is not possible with mongodb version 3.4 but if you upgrade to 3.6 then you can try below aggregation
db.collection.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(req.params.userId) } },
{ "$lookup": {
"from": Meals.collection.name,
"let": { "meal_id": "$dietPlans.dietPlanCurrent.monday.breakfast.meal" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$meal_id" ] } } },
{ "$unwind": "$ingredients" },
{ "$lookup": {
"from": Ingredients.collection.name,
"let": { "ingredient_id": "$ingredients.ingredient" },
"pipeline": [
{ "$match": { "$expr": { "$eq": [ "$_id", "$$ingredient_id" ] } } }
],
"as": "matchedIngredients"
}},
{ "$unwind": "$ingredients.matchedIngredients" },
{ "$group": {
"_id": "$_id",
"name": { "$first":"$name" },
"cuisine": { "$first":"$cuisine" },
"ingredients": { "$push":"$ingredients" }
}}
],
"as": "dietPlans.dietPlanCurrent.monday.breakfast.mealObject"
}},
{ "$unwind": "$dietPlans.dietPlanCurrent.monday.breakfast.mealObject" }
])

Categories

Resources