How to find mongo ObjectId from Map? - mongodb

I am facing one issue where I need to find ObjectId which is basically a user Id and need to perform a lookup from other collection called "tb_user" from that user Id.
I tried many times but not found a optimised way.
below I is the detail what I tried.
first match the conditions.
perform projection which matches only one object from the list.
unwind subtask array
remove unwanted(only in this operation) keys form object.
again convert to array of object which contain key value pairs.
remove the values from object.
unwind again subtask.
In project take userId from keys.
convert it to objectId.
perform the lookup from tb_user collection.
at last get user information.
db.getCollection('tb_task').aggregate([
{
"$match": { "$and": [{ "_id" : ObjectId("5d766ac6f3195ed8e7361b62")},
{'sub_tasks.name': "task_1_19Z50DZYDV"}] },
},
{
"$project": {
"sub_tasks": {
"$filter": {
"input": "$sub_tasks",
"as": "sub_task",
"cond": {
"$and":{ "$eq": [
"$$sub_task.name","task_1_19Z50DZYDV"
]
}
}
}
}
}
},
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{
"$project": {
"_id":0,
"sub_tasks.name":0,
"sub_tasks.total_queries":0,
"sub_tasks.total_query_result_pairs":0,
"sub_tasks.total_assigned_to":0
}
},
{ $project: { "sub_tasks" : { $objectToArray: "$sub_tasks" } } },
{ $project: { "sub_tasks.v" : 0} },
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_id" : "$sub_tasks.k"}},
{
"$addFields": {
"user_id": {
"$toObjectId": "$user_id"
}
}
},
{ "$lookup": {
"from": "tb_user",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{
"$unwind": {
"path": "$user",
preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_name" : "$user.first_name",
"is_active" : "$user.is_active",
"user_id" : "$user._id",
}
}
])
Actual collection schema
{
"_id" : ObjectId("5d766ac6f3195ed8e7361b62"),
"name" : "task_1",
"sub_tasks" : [
{
"name" : "task_1_19Z50DZYDV",
"total_queries" : 10,
"total_query_result_pairs" : 20,
"total_assigned_to" : 2,
"5d6e387524d8bd317909806a" : {
"assigned_time" : ISODate("2019-09-09T15:41:26.000Z"),
"finished_time" : null,
"is_finished" : false,
"total_queries_rated" : 0
},
"5d6e387524d8bd31790ab4" : {
"assigned_time" : ISODate("2019-09-09T15:41:26.000Z"),
"finished_time" : null,
"is_finished" : false,
"total_queries_rated" : 0
}
}
]
}
Excepted output
{
"user_name" : "Heey",
"is_active" : true,
"user_id" : ObjectId("5d6e387524d8bd317909806a")
},
{
"user_name" : "ram",
"is_active" : true,
"user_id" : ObjectId("5d6e387524d8bd31790ab4")
}

Since _id is unique for each document, you can apply just
{
"$match": {
"_id" : ObjectId("5d766ac6f3195ed8e7361b62")
}
}
And also it will return only one document, no need to use filter in $project instead, use :
{
$project:{
"sub_tasks":1
}
}
and after unwind match need to be added so that it will send you the documents which contain the "sub_task.name" = "task_1_19Z50DZYDV".
{
$match:{
"sub_task.name":"task_1_19Z50DZYDV"
}
}
Rest of the stages seem good to me.
So, the final query will look like this:
db.getCollection('tb_task').aggregate([
{
"$match": {
"_id" : ObjectId("5d766ac6f3195ed8e7361b62")
}
},
{
$project:{
"sub_tasks":1
}
},
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{
$match:{
"sub_task.name":"task_1_19Z50DZYDV"
}
},
{
"$project": {
"_id":0,
"sub_tasks.name":0,
"sub_tasks.total_queries":0,
"sub_tasks.total_query_result_pairs":0,
"sub_tasks.total_assigned_to":0
}
},
{ $project: { "sub_tasks" : { $objectToArray: "$sub_tasks" } } },
{ $project: { "sub_tasks.v" : 0} },
{
"$unwind": {
"path": "$sub_tasks",
"preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_id" : "$sub_tasks.k"}},
{
"$addFields": {
"user_id": {
"$toObjectId": "$user_id"
}
}
},
{ "$lookup": {
"from": "tb_user",
"localField": "user_id",
"foreignField": "_id",
"as": "user"
}
},
{
"$unwind": {
"path": "$user",
preserveNullAndEmptyArrays": true
}
},
{ $project: { "user_name" : "$user.first_name",
"is_active" : "$user.is_active",
"user_id" : "$user._id",
}
}
])
And for understanding more tips of optimizing the aggregation pipelines refer here

Related

MongoDB - Aggregate (join two collection) with nested array as join key

I have two collections. (updated)
I need to join them and return only a few fields.
As I understand, I should use aggregate and projection features.
I try to find examples, but can't find the right, in my case foreign key is placed in the array in the nested field.
It is not easy for me, I’m new at this, and I couldn’t win against this MongoDB query.
In the code block below I wrote the simplified equivalent models (doc1, doc2) and expected results.
Does anyone have any ideas?
db.doc1.deleteMany({});
db.doc2.deleteMany({});
// tow related documents
db.doc1.insert( [
{
"version" : 123456,
"doc" : {
"code":"A1",
"name":"some document A1",
"doc2CodeArray":[
{"code":"B01"},
{"code":"B02"},
{"code":"B03"},
{"code":"B04"},
{"code":"B05"},
{"code":"B06"}
]
}
},
{
"version" : 123457,
"doc" : {
"code":"A2",
"name":"some document A2",
"doc2CodeArray":[
{"code":"B07"},
{"code":"B08"},
{"code":"B09"},
{"code":"B10"},
{"code":"B11"},
{"code":"B12"}
]
}
},
{
"version" : 123457,
"doc" : {
"code":"A2",
"name":"some document A2",
"doc2CodeArray":null
}
}
]);
db.doc2.insert( [
{
"version" : 567890,
"doc" : {
"code":"B01",
"valueArray":[{"valueType":"int","valueData":"1"}],
"doc2Type":{"code":"C1"}
}
},
{"version" : 567890,"doc" : { "code":"B02", "valueArray":[{"valueType":"int","valueData":"2","isDefault":false}],"doc2Type":{"code":"C2","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B03", "valueArray":[{"valueType":"int","valueData":"3","isDefault":false}],"doc2Type":{"code":"C3","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B04", "valueArray":[{"valueType":"int","valueData":"4","isDefault":false}],"doc2Type":{"code":"C4","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B05", "valueArray":[{"valueType":"int","valueData":"5","isDefault":false}],"doc2Type":{"code":"C5","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B06", "valueArray":[{"valueType":"int","valueData":"6","isDefault":false},
{"valueType":"str","valueData":"F","isDefault":false}],"doc2Type":{"code":"C6","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B07", "valueArray":[{"valueType":"int","valueData":"1","isDefault":false}],"doc2Type":{"code":"C1","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B08", "valueArray":[{"valueType":"int","valueData":"2","isDefault":false}],"doc2Type":{"code":"C2","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B09", "valueArray":[{"valueType":"int","valueData":"3","isDefault":false}],"doc2Type":{"code":"C3","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B10", "valueArray":[{"valueType":"int","valueData":"4","isDefault":false}],"doc2Type":{"code":"C4","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B11", "valueArray":[{"valueType":"int","valueData":"5","isDefault":false}],"doc2Type":{"code":"C5","addInfo":"1234567890"}}},
{"version" : 567890,"doc" : { "code":"B12", "valueArray":[{"valueType":"int","valueData":"6","isDefault":false}],"doc2Type":{"code":"C6","addInfo":"1234567890"}}},
]);
The result that I want
I need join collections and select only few props
{
"doc":{code:"A1"},
"doc2Items":[
{"code":"C2", "value":{"value":"3"}},
{"code":"C3", "value":{"value":"4"}},
]
},
{
"doc":{code:"A2"},
"doc2Items":[
{"code":"C2", "value":{"value":"2"}},
{"code":"C3", "value":{"value":"3"}},
{"code":"C4", "value":{"value":"4"}},
]
}
https://mongoplayground.net/p/1-1SU8SgbTQ
db.doc1.aggregate([
{
$lookup: {
from: "doc2",
localField: "doc.doc2CodeArray.code",
foreignField: "doc.code",
as: "doc.doc2Items"
}
},
{
$replaceRoot: {
newRoot: "$doc"
}
},
{
$addFields: {
"doc2Items": {
$map: {
input: "$doc2Items",
in: {
doc2TypeCode: "$$this.doc.doc2Type.code",
doc2ValueArray: "$$this.doc.valueArray"
}
}
}
}
},
{
$unset: [
"doc2CodeArray"
]
}
])
$lookup - Join doc1 with doc2.
Pipeline:
1.1. $match - Matching doc.code (from doc2) is within the doc.doc2CodeArray.code array (from doc1).
1.2. $project - Decorate output document to be returned in doc2Items.
$project - Decorate output document.
db.doc1.aggregate([
{
$lookup: {
from: "doc2",
let: {
doc2CodeArray: "$doc.doc2CodeArray.code"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$doc.code",
"$$doc2CodeArray"
]
}
}
},
{
$project: {
_id: 0,
"doc2TypeCode": "$doc.doc2Type.code",
"doc2ValueArray": "$doc.valueArray"
}
}
],
as: "doc2Items"
}
},
{
$project: {
_id: 0,
"doc1Code": "$doc.code",
"doc1Name": "$doc.name",
"doc2Items": "$doc2Items"
}
}
])
Sample Mongo Playground
Last few days I deep dived to reading.
And I have got an accepted result.
I updated my question with example of more relevan data and I has given the my solution in the example below.
db.doc1.aggregate([
{
$match: {
// filter by doc.code preffix
"doc.code": { '$regex': 'A', '$options': 'i' }, "doc.doc2CodeArray": { $ne: null }
//"doc.code": "A1"
}
},
{
$lookup: { // join doc1 & doc2
from: "doc2",
localField: "doc.doc2CodeArray.code",
foreignField: "doc.code",
as: "doc2Items"
}
},
{
$project: {
_id: 0,
"doc.code": 1,
"doc2Items": { // items from doc2
$map: { // I need not all props from doc2 - do $map
"input": {
$filter: { // I need items only with 3 codes C2, C3, C4 - do $filter
"input": "$doc2Items",
"as": "d0",
"cond": { $or:[
{ "$eq": ["$$d0.doc.doc2Type.code", "C2"] },
{ "$eq": ["$$d0.doc.doc2Type.code", "C3"] },
{ "$eq": ["$$d0.doc.doc2Type.code", "C4"] },
]}
},
},
"as": "d",
"in": {
"code": "$$d.doc.doc2Type.code",
"value": {
$arrayElemAt: [ // I need only first record of nested array
{
$map: { // I need not all props of first record of nested array
"input": "$$d.doc.valueArray",
"as": "d2",
"in": {
"value": "$$d2.valueData"
}
}
}
, 0]
},
}
}// map
}
}// project
}
])
Now I read the needed data two time faster then before.
I think I began to understand mongo db query philosophy.
Thank everyone for the answers and tips! 'Yong Shun' and 'qtxo' helped me find the correct solution.

Agregate query with lookup and restriction condition in MongoDb

I have a collection myCollection
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
with this data :
"_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")
}
],
and a collection members
{
firstname : String
lastname : String
}
with this data :
[{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"lastname" : "besbes"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"lastname" : "besbes"
}]
I make a query with aggregate and $lookup, to have the data populated but I want to restrict returned
data on status 1 only, this is my query and result.
how can I get data populated with only status 1 members returned ? Thank you.
query
db.getCollection('myCollection').aggregate([
{ $match: { _id: ObjectId("5e8b0bac041a913bc608d69d")}},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname" : 1,
"Members.lastname" : 1,
"Members._id" : 1,
},
}
])
result
{
"_id" : ObjectId("5e8b0bac041a913bc608d69d"),
"Members" : [
{
"_id" : ObjectId("5e7dbf5b257e6b18a62f2da9"),
"firstname" : "raed",
"lastname" : "besbes"
},
{
"_id" : ObjectId("5e7e2f048f80b46d786bfd67"),
"firstname" : "sarra",
"lastname" : "besbes"
}
]
}
Option 1
Filter members before $lookup
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
$addFields: {
members: {
$filter: {
input: "$members",
cond: {
$eq: [
"$$this.status",
1
]
}
}
}
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
"Members.firstname": 1,
"Members.lastname": 1,
"Members._id": 1
}
}
])
MongoPlayground
Option 2
(Similar to 1) We flatten member array, filter only status = 1 and then perform $lookup.
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
"$unwind": "$members"
},
{
$match: {
"members.status": 1
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
"$unwind": "$Members"
},
{
$group: {
_id: "$_id",
Members: {
$push: "$Members"
}
}
}
])
MongoPlayground
Option 3
We can apply filter for Member array based on filtered values for member array.
db.myCollection.aggregate([
{
$match: {
_id: ObjectId("5e8b0bac041a913bc608d69d")
}
},
{
"$lookup": {
"from": "members",
"localField": "members.memberId",
"foreignField": "_id",
"as": "Members"
}
},
{
$project: {
Members: {
$filter: {
input: "$Members",
cond: {
$in: [
"$$this._id",
{
$let: {
vars: {
input: {
$filter: {
input: "$members",
cond: {
$eq: [
"$$this.status",
1
]
}
}
}
},
in: "$$input.memberId"
}
}
]
}
}
}
}
}
])
MongoPlayground

Pymongo: Aggregate all documents that not have one field and have another - group by

I'm trying to get a set of documents from my collection with the following conditions:
field: img_status doesn't exist
field: images should exist
And then, group those documents (unique/distinct) by one field.
When I execute the query in MongoDB, it seems as if it returns the correct values:
db.getCollection('products').aggregate([
{ $match: { images: { $exists: true, $ne: null } } },
{ $match: { img_status: { $exists: false } } },
{ $group : { _id:"$vendor_link", "uuid" : {$first: "$uuid"}, "images": { $first: "$images"} } }
])
But in PyMongo, I always get the opposite like: img_status exist = true:
pipeline = [
{
"$match":
{
"images" :
{ "$ne" : "null", "$exists": "true", }
}
},
{
"$match":
{
"img_status":
{"$exists": "false"}
}
},
{
"$group":
{
"_id" : "$vendor_link",
"images" : {"$first": "$images"},
"uuid" : {"$first": "$uuid"},
"source" : {"$first": "$source"}
}},
]
pprint(list(self.collection.aggregate(pipeline)))
What am I doing wrong?
This should fix the issue. You are passing string values instead of None and boolean values True and False
pipeline = [
{
"$match": {
"images" : {
"$ne" : None,
"$exists": True,
}
}
},
{
"$match": {
"img_status": {
"$exists": False,
}
}
},
{
"$group":{
"_id" : "$vendor_link",
"images" : {"$first": "$images"},
"uuid" : {"$first": "$uuid"},
"source" : {"$first": "$source"}
}
},
]
pprint(list(self.collection.aggregate(pipeline)))

How to push all values in single array in mongodb

Colleges
/* 1 createdAt:5/9/2019, 7:00:04 PM*/
{
"_id" : ObjectId("5cd42b5c65b41027845938ae"),
"clgID" : "100",
"name" : "Anna University"
},
/* 2 createdAt:5/9/2019, 7:00:04 PM*/
{
"_id" : ObjectId("5cd42b5c65b41027845938ad"),
"clgID" : "200",
"name" : "National"
}
Subjects:
/* 1 createdAt:5/9/2019, 7:03:24 PM*/
{
"_id" : ObjectId("5cd42c2465b41027845938b0"),
"name" : "Hindi",
"members" : {
"student" : [
"123"
]
},
"college" : {
"collegeID" : "100"
}
},
/* 2 createdAt:5/9/2019, 7:03:24 PM*/
{
"_id" : ObjectId("5cd42c2465b41027845938af"),
"name" : "English",
"members" : {
"student" : [
"456",
"789"
]
},
"college" : {
"collegeID" : "100"
}
}
Here i am having two collection and i want to join Colleges table is clgID and Subjects table iscollege.collegeID , then i want to take members.student values and push into single array based on college.collegeID.
My Expected Output
{
"GroupDetails" : [ ],
"clgName" : "National"
},
{
"GroupDetails" : [
"123",
"456",
"789"
],
"clgName" : "Anna University"
}
My Code
db.Colleges.aggregate([
{ $match : { "clgID" : { $in : ["100", "200"] } } },
{ $lookup: { from: "Subjects", localField: "clgID", foreignField: "college.collegeID", as: "GroupDetails" } },
//{ $unwind: "$GroupDetails" },
{ $project: { '_id' : false, 'clgName' : '$name', 'GroupDetails.members.student' : true } }
])
I am getting like this
/* 1 */
{
"GroupDetails" : [ ],
"clgName" : "National"
},
/* 2 */
{
"GroupDetails" : [
{
"members" : {
"student" : [
"456"
]
}
},
{
"members" : {
"student" : [
"123"
]
}
}
],
"clgName" : "Anna University"
}
You can use below aggregation with mongodb 3.6 and above
db.Colleges.aggregate([
{ "$match": { "clgID": { "$in": ["100", "200"] } } },
{ "$lookup": {
"from": "Subjects",
"let": { "clgId": "$clgID" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$$clgId", "$college.collegeID"] } } },
{ "$group": {
"_id": "$college.collegeID",
"groupDetails": { "$push": "$members.student" }
}},
{ "$project": {
"groupDetails": {
"$reduce": {
"input": "$groupDetails",
"initialValue": [],
"in": { "$concatArrays": ["$$this", "$$value"] }
}
}
}}
],
"as": "clg"
}},
{ "$unwind": { "path": "$clg", "preserveNullAndEmptyArrays": true } },
{ "$project": {
"clgName": "$name",
"groupDetails": { "$ifNull": ["$clg.groupDetails", []] }
}}
])
MongoPlayground
Or with the mongodb 3.4 and below
db.Colleges.aggregate([
{ "$match": { "clgID": { "$in": ["100", "200"] }}},
{ "$lookup": {
"from": "Subjects",
"localField": "clgID",
"foreignField": "college.collegeID",
"as": "clg"
}},
{ "$unwind": { "path": "$clg", "preserveNullAndEmptyArrays": true }},
{ "$group": {
"_id": { "clgId": "$clg.college.collegeID", "_id": "$_id" },
"groupDetails": { "$push": "$clg.members.student" },
"clgName": { "$first": "$name" }
}},
{ "$project": {
"_id": "$_id._id",
"clgName": 1,
"groupDetails": {
"$reduce": {
"input": "$groupDetails",
"initialValue": [],
"in": { "$concatArrays": ["$$this", "$$value"] }
}
}
}}
])
MongoPlayground

MongoDB join data inside an array of objects

I have document like this in a collection called diagnoses :
{
"_id" : ObjectId("582d43d18ec3f432f3260682"),
"patientid" : ObjectId("582aacff3894c3afd7ad4677"),
"doctorid" : ObjectId("582a80c93894c3afd7ad4675"),
"medicalcondition" : "high fever, cough, runny nose.",
"diagnosis" : "Viral Flu",
"addmissiondate" : "2016-01-12",
"dischargedate" : "2016-01-16",
"bhtno" : "125",
"prescription" : [
{
"drug" : ObjectId("58345e0e996d340bd8126149"),
"instructions" : "Take 2 daily, after meals."
},
{
"drug" : ObjectId("5836bc0b291918eb42966320"),
"instructions" : "Take 1 daily, after meals."
}
]
}
The drug id inside the prescription object array is from a separate collection called drugs, see sample document below :
{
"_id" : ObjectId("58345e0e996d340bd8126149"),
"genericname" : "Paracetamol Tab 500mg",
"type" : "X",
"isbrand" : false
}
I am trying to create a mongodb query using the native node.js driver to get a result like this:
{
"_id" : ObjectId("582d43d18ec3f432f3260682"),
"patientid" : ObjectId("582aacff3894c3afd7ad4677"),
"doctorid" : ObjectId("582a80c93894c3afd7ad4675"),
"medicalcondition" : "high fever, cough, runny nose.",
"diagnosis" : "Viral Flu",
"addmissiondate" : "2016-01-12",
"dischargedate" : "2016-01-16",
"bhtno" : "125",
"prescription" : [
{
"drug" :
{
"_id" : ObjectId("58345e0e996d340bd8126149"),
"genericname" : "Paracetamol Tab 500mg",
"type" : "X",
"isbrand" : false
},
"instructions" : "Take 2 daily, after meals."
},
...
]
}
Any advice on how to approach a similar result like this is much appreciated, thanks.
Using MongoDB 3.4.4 and newer
With the aggregation framework, the $lookup operators supports arrays
db.diagnoses.aggregate([
{ "$addFields": {
"prescription": { "$ifNull" : [ "$prescription", [ ] ] }
} },
{ "$lookup": {
"from": "drugs",
"localField": "prescription.drug",
"foreignField": "_id",
"as": "drugs"
} },
{ "$addFields": {
"prescription": {
"$map": {
"input": "$prescription",
"in": {
"$mergeObjects": [
"$$this",
{ "drug": {
"$arrayElemAt": [
"$drugs",
{
"$indexOfArray": [
"$drugs._id",
"$$this.drug"
]
}
]
} }
]
}
}
}
} },
{ "$project": { "drugs": 0 } }
])
For older MongoDB versions:
You can create a pipeline that first flattens the prescription array using the $unwind operator and a $lookup subsequent pipeline step to do a "left outer join" on the "drugs" collection. Apply another $unwind operation on the created array from the "joined" field. $group the previously flattened documents from the first pipeline where there $unwind operator outputs a document for each element in the prescription array.
Assembling the above pipeline, run the following aggregate operation:
db.diagnoses.aggregate([
{
"$project": {
"patientid": 1,
"doctorid": 1,
"medicalcondition": 1,
"diagnosis": 1,
"addmissiondate": 1,
"dischargedate": 1,
"bhtno": 1,
"prescription": { "$ifNull" : [ "$prescription", [ ] ] }
}
},
{
"$unwind": {
"path": "$prescription",
"preserveNullAndEmptyArrays": true
}
},
{
"$lookup": {
"from": "drugs",
"localField": "prescription.drug",
"foreignField": "_id",
"as": "prescription.drug"
}
},
{ "$unwind": "$prescription.drug" },
{
"$group": {
"_id": "$_id",
"patientid" : { "$first": "$patientid" },
"doctorid" : { "$first": "$doctorid" },
"medicalcondition" : { "$first": "$medicalcondition" },
"diagnosis" : { "$first": "$diagnosis" },
"addmissiondate" : { "$first": "$addmissiondate" },
"dischargedate" : { "$first": "$dischargedate" },
"bhtno" : { "$first": "$bhtno" },
"prescription" : { "$push": "$prescription" }
}
}
])
Sample Output
{
"_id" : ObjectId("582d43d18ec3f432f3260682"),
"patientid" : ObjectId("582aacff3894c3afd7ad4677"),
"doctorid" : ObjectId("582a80c93894c3afd7ad4675"),
"medicalcondition" : "high fever, cough, runny nose.",
"diagnosis" : "Viral Flu",
"addmissiondate" : "2016-01-12",
"dischargedate" : "2016-01-16",
"bhtno" : "125",
"prescription" : [
{
"drug" : {
"_id" : ObjectId("58345e0e996d340bd8126149"),
"genericname" : "Paracetamol Tab 500mg",
"type" : "X",
"isbrand" : false
},
"instructions" : "Take 2 daily, after meals."
},
{
"drug" : {
"_id" : ObjectId("5836bc0b291918eb42966320"),
"genericname" : "Paracetamol Tab 100mg",
"type" : "Y",
"isbrand" : false
},
"instructions" : "Take 1 daily, after meals."
}
]
}
In MongoDB 3.6 or later versions
It seems that
$lookup will overwrite the original array instead of merging it.
A working solution (a workaround, if you prefer) is to create a different field,
and then merge two fields, as shown below:
db.diagnoses.aggregate([
{ "$lookup": {
"from": "drugs",
"localField": "prescription.drug",
"foreignField": "_id",
"as": "prescription_drug_info"
} },
{ "$addFields": {
"merged_drug_info": {
"$map": {
"input": "$prescription",
"in": {
"$mergeObjects": [
"$$this",
{ "$arrayElemAt": [
"$prescription_drug_info._id",
"$$this._id"
] }
]
}
}
}
} }
])
This would add two more fields and the name of the desired field
will be merged_drug_info. We can then add $project stage to filter
out excessive fields and $set stage to rename the field:
...
{ "$set": { "prescription": "$merged_drug_info" } },
{ "$project": { "prescription_drug_info": 0, "merged_drug_info": 0 } }
...