MongoDB Aggregate two collections and flatten the second collection - mongodb

Collection - 1 (users)
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"userConsent" : null,
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor"
}
Collection -2 (2 documents)
/* 1 */
{
"_id" : ObjectId("62e9296bf06b3bfdc93be0da"),
"lastUpdated" : "2022-08-02T13:40:59.265Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"docType" : "dose2",
"administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"uploadTimestamp" : "2022-08-02T13:40:59.265Z",
"manufacturer" : "Novavax"
}
/* 2 */
{
"_id" : ObjectId("62e911cf5b6e67c36c4f843e"),
"lastUpdated" : "2022-08-02T12:00:15.201Z",
"email" : "aandtv10#gmail.com",
"userName" : "Aand Aa",
"templateName" : "Covid-Vaccine",
"docType" : "dose1",
"exemptType" : null,
"administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"uploadTimestamp" : "2022-08-02T12:00:15.202Z",
"manufacturer" : "Novavax"
}
Expected Result:
{
"_id" : "aandtv10#gmail.com",
"updateTimestamp" : "2022-07-19T11:59:18.029Z",
"firstName" : "Aand",
"lastName" : "Aa",
"fullName" : "Aand Aa",
"orgRootName" : "Black&Veatch",
"relationType" : "Contractor",
"dose1_administeredTimestamp" : "2022-08-23T17:16:20.347Z",
"dose1_manufacturer" : "Novavax",
"dose2_administeredTimestamp" : "2022-10-02T11:10:55.784Z",
"dose2_manufacturer" : "Novavax"
}
Query Used:
db.users.aggregate([{
"$match": {
"_id": "aandtv10#gmail.com"
}
},{
$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}}, { $unwind: "$documents" },{
"$group": {
"fullName": { $max: "$identity.fullName" },
"_id":"$_id",
"relationship": { $max:"$seedNetwork.relationType" },
"registration_date": { $max:"$seedNetwork.seededTimestamp" },
"vaccination_level": { $max: "" },
"exemption_declination_date": { $max:"N/A" },
"exemption_verification": { $max: "N/A" },
"dose1_date" : { $max: { $cond: { if: {$eq: ["$docType", "dose1" ]}, then: "$administeredTimestamp", else: "<false-case>" } } }
}
}
]);

One option is using $reduce with $arrayToObject:
db.users.aggregate([
{$match: {_id: "aandtv10#gmail.com"}},
{$set: {data: "$$ROOT"}},
{$lookup: {
from: "documents",
localField: "_id",
foreignField: "email",
as: "documents"
}
},
{$project: {
_id: 0,
data: 1,
documents: {
$reduce: {
input: "$documents",
initialValue: [],
in: {
$concatArrays: [
"$$value",
[
{k: {$concat: ["$$this.docType", "_administeredTimestamp"]},
v: "$$this.administeredTimestamp"},
{k: {$concat: ["$$this.docType", "_manufacturer"]},
v: "$$this.manufacturer"}
]
]
}
}
}
}
},
{$replaceRoot: {newRoot: {$mergeObjects: ["$data", {$arrayToObject: "$documents"}]}}}
])
See how it works on the playground example

Related

How to fetch all fields of parent document?

I have tried to fetch all fields of parent document using $group but it send only specific fields.
Another Question. How to fetch all fields of parent document using '$project'?
Collection "Opportunity"
Document #1
{
"_id" : ObjectId("5c42b737cd94891a57fbf33e"),
"accountId" : [
ObjectId("5c29f198b248d845931a1830"),
ObjectId("5c29f198b248d845931a1831"),
]
"name" : "OppNewTest01",
"value" : 10000,
"status" : "open",
}
Collection "Account"
Document #1
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
}
My MongoDB Query,
con.collection('opportunity').aggregate([
{
$unwind: "account"
},
{
$lookup: {
from: 'account',
localField: 'accountId',
foreignField: '_id',
as: 'accounts',
},
},
{
$unwind: '$accounts'
},
{
$group: {
"_id": "$_id",
"account": { "$push": "$accounts" }
}
},
]).toArray((err, res) => {
cbFun(res, err);
});
Output of the above query,
{
"_id" : "5c42b737cd94891a57fbf33e",
"account": [
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
},
{
"_id" : ObjectId("5c29f198b248d845931a1831"),
"name" : "MyTestAcc1",
"phone" : "7845124579",
},
]
}
I want to below output,
{
"_id" : "5c42b737cd94891a57fbf33e",
"accountId" : [
ObjectId("5c29f198b248d845931a1830"),
ObjectId("5c29f198b248d845931a1831"),
],
"name" : "OppNewTest01",
"value" : 10000,
"status" : "open",
"account": [
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
},
{
"_id" : ObjectId("5c29f198b248d845931a1831"),
"name" : "MyTestAcc1",
"phone" : "7845124579",
},
]
}
I have tried to fetch above output but not getting.
You are just losing that data when you $group, save those fields in that stage to keep them.
{
$group: {
"_id": "$_id",
"account": { "$push": "$accounts" },
value: {$first: "$value"},
status: {$first: "$status},
name: {$first: "$name"},
accountId: {$push: "$accountId},
}
}
EDIT:
[
{
$group: {
"_id": "$_id",
"accounts": { "$push": "$accounts" },
"doc": {$first: "$$ROOT"},
}
},
{
$addFields: {
"doc.accounts" :"$accounts"
}
}
]

How to use group by in mongodb?

I want to values in comma seperated.So how to use group/concat in aggregate framework?
Document contact
{
"_id" : "9c612c95054fb46e3de8dee8",
"name" : "name1",
"oppID" : [
"5c612c95054fb46e3de8bcc5",
"5bd6b334cba7d2241a3ba9d9"
]
},
{
"_id" : "1c612c95054fb46e3de8dcde9",
"name" : "name2",
"oppID" : [
"5c612c95054fb46e3de8bcc5",
"5bd6b334cba7d2241a3ba9d9"
]
}
Document opportunity
{
"_id" : "5c612c95054fb46e3de8bcc5",
"name" : "opp name 01",
},
{
"_id" : "5bd6b334cba7d2241a3ba9d9",
"name" : "opp name 02",
},
I want to get contact list like below,
{
"_id" : "9c612c95054fb46e3de8dee8",
"oppName" : "opp name 01, opp name 02"
},
{
"_id" : "1c612c95054fb46e3de8dcde9",
"oppName" : "opp name 01, opp name 02"
}
You need to run $lookup to get the data from both collections and then $reduce with $concat to transform an array into single string
db.contact.aggregate([
{
$lookup: {
from: "opportunity",
localField: "oppID",
foreignField: "_id",
as: "opportunities"
}
},
{
$project: {
_id: 1,
oppName: {
$reduce: {
input: "$opportunities",
initialValue: "",
in: {
$cond: [ { $eq: [ "$$value", "" ] }, "$$this.name", { $concat: [ "$$value", ", ", "$$this.name" ] } ]
}
}
}
}
}
])

MongoDB How to Find out the records which are not in between the date range

I have two collections like below,
user Collection
{
"_id" : ObjectId("5af2e946aa546125b5de85cc"),
"name" : "Sudhin",
"email" : "abc#abc.com",
"roles" : [
"Reader",
"Instructor"
],
"createdAt" : ISODate("2018-05-09T12:27:50.651Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.280Z")
},
{
"_id" : ObjectId("5af2f3a6efb83031faaa3d82"),
"name" : "Rahul",
"email" : "abcd#abc.com",
"roles" : [
"Reader",
"Instructor"
]
"createdAt" : ISODate("2018-05-09T13:12:06.518Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.281Z")
}
schedulers Collections
{
"_id" : ObjectId("5afd763b8fad29597e1b85ed"),
"title" : "ILT Course",
"type" : "Course",
"ilt" : {
"instructorId" : ObjectId("5af2e946aa546125b5de85cc"),
"type" : "ILT-Offline",
"instructorName" : "Sudhin",
"place" : "*******",
"description" : "******"
},
"startDate" : ISODate("2018-05-10T11:00:00.000Z"),
"endDate" : ISODate("2018-05-15T12:00:00.000Z"),
"createdAt" : ISODate("2018-05-17T12:31:55.574Z"),
"updatedAt" : ISODate("2018-05-17T12:31:55.575Z")
}
In the scheduler collection "ilt.instructorId" is the referenceId for user.
Scheduler collection is having all the details of a particular user schedule.
startDate is the starting date and time of a particular schedule.
endDate is the ending date and time of a particular schedule.
When I pass startDate to endDate(2018-05-05 00:00 - 2018-05-10 00:00), I want to fetch all the users with role instructor and who do not have any scheduled courses in between those dates.
Eg: If I'm passing 2018-05-05 00:00 - 2018-05-10 00:00 it should return
the below document
{
"_id" : ObjectId("5af2f3a6efb83031faaa3d82"),
"name" : "Rahul",
"email" : "abcd#abc.com",
"roles" : [
"Reader",
"Instructor"
]
"createdAt" : ISODate("2018-05-09T13:12:06.518Z"),
"updatedAt" : ISODate("2018-05-16T09:22:07.281Z")
}
I have tried the below query
UserModel.query().aggregate([
{ $match: { roles: { $in: ['Instructor'] } } },
{
$lookup: {
from: "schedulers",
localField: "_id",
foreignField: "ilt.instructorId",
as: "schedule"
}
},
{
$match: {
"schedule.type": "Course",
$and: [
{ 'schedule.endDate': { $not: { $lte: new Date("2018-05-12T12:00:00.000Z") } }},
{ 'schedule.startDate': { $not: { $gte: new Date("2018-05-06T11:00:00.000Z") } }},
]
}
},
{
$project: {
_id: 1,
name: 1,
empId: 1,
startDateTime: "$schedule.startDate",
endDateTime: "$schedule.endDate",
}
}])
Please check below query its help you:
db.getCollection('user').aggregate([
{ $match: { roles: { $in: ['Instructor'] } } },
{
$lookup: {
from: "schedulers",
localField: "_id",
foreignField: "ilt.instructorId",
as: "schedule"
}
},
{
$match: {
"schedule.type": "Course",
$or: [
{ 'schedule.startDate': { $lt: new Date("2018-05-08T11:00:00.000Z") }},
{ 'schedule.endDate': { $gt: new Date("2018-05-14T12:00:00.000Z") }},
]
}
},
{
$unwind: {
path: '$schedule',
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
_id: 1,
name: 1,
empId: 1,
schedule:'$schedule',
startDateTime: "$schedule.startDate",
endDateTime: "$schedule.endDate",
}
}])

Aggregate by array field with object ids

Is there a better way of retrieving values from a collection2 based on an array field with object ids in collection1? I've tried to use $project but failed to get all required fields
Collections to aggregate:
collection1:
{
"_id" : ObjectId("5a58910de202796cfef41c6a"),
"sortOrder" : 5,
"title" : "Question 1 ?",
"freeTextIncluded" : false,
"freeText" : false,
"resultChart" : "pieChart",
"answer" : [
ObjectId("5a579fefd5554706b446cc71"),
ObjectId("5a587f17e4b2de0d683f96a4"),
ObjectId("5a587f20e4b2de0d683f96a5"),
ObjectId("5a587f29e4b2de0d683f96a6")
],
"state" : "active",
"__v" : 1,
"description" : ""
}
collection2:
{
"_id" : ObjectId("5a579fefd5554706b446cc71"),
"slug" : "answer-1",
"title" : "Answer 1",
"state" : "active",
"__v" : 0,
"author" : ObjectId("5a2e6b56e593c8525ced34b8"),
"body" : "<p>Lipsum...</p>"
}
{
"_id" : ObjectId("5a587f17e4b2de0d683f96a4"),
"slug" : "answer-2",
"title" : "Answer 2",
"state" : "active",
"__v" : 0,
"body" : ""
}
{
"_id" : ObjectId("5a587f20e4b2de0d683f96a5"),
"slug" : "answer-3",
"title" : "Answer 3",
"state" : "active",
"__v" : 0,
"body" : "",
"isCorrect" : true,
"sortOrder" : 3
}
{
"_id" : ObjectId("5a587f29e4b2de0d683f96a6"),
"slug" : "answer-4",
"title" : "Answer 4",
"state" : "active",
"__v" : 0,
"body" : ""
}
This aggregation works ok but I'm just wondering if there's a better/shorter way of aggregating 2 collections...
db.getCollection('questions').aggregate([
{
$match: {'_id': ObjectId('5a58910de202796cfef41c6a') }
},
{
$unwind: "$answer"
},
{
$lookup:
{
from: "answers",
localField: "answer",
foreignField: "_id",
as: "answers"
}
},
{
$match: { "answers": { $ne: [] }}
},
{
$unwind: "$answers"
},
{
$group: {
_id : ObjectId('5a58910de202796cfef41c6a'),
answerList: {$push: "$answers"},
title: {$first: "$title"},
sortOrder: {$first: "$sortOrder"},
description: {$first: "$description"},
resultChart: {$first: "$resultChart"},
freeTextIncluded: {$first: "$freeTextIncluded"},
}
}
]);
You need to improve your query like this:
db.getCollection('test').aggregate([{
$match: {
'_id': ObjectId('5a58910de202796cfef41c6a')
}
},
{
$lookup: {
from: "answers",
localField: "answer",
foreignField: "_id",
as: "answers"
}
},
{
$unwind: {
path: "$answers",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: ObjectId('5a58910de202796cfef41c6a'),
answerList: {
$push: "$answers"
},
title: {
$first: "$title"
},
sortOrder: {
$first: "$sortOrder"
},
description: {
$first: "$description"
},
resultChart: {
$first: "$resultChart"
},
freeTextIncluded: {
$first: "$freeTextIncluded"
},
}
}])

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"
}
}