I have below user details in my bookings collection
{
"_id" : ObjectId("609a382b589346973c84c6fe"),
"Name" : "abc",
"UserId":1
"Status" : "Pending",
"BookingData" : {
"Date" : ISODate("2021-04-30T04:00:00.000Z"),
"info" : [],
"BookingDataMethod" : "avf",
"Message" : null,
"products" : [
{
"_id" : ObjectId("60a4e92775e5de3570578820"),
"ProductName" : "Test1",
"ProductID" : ObjectId("60a4e92475e5de357057880a"),
"IsDeliveryFailed" : "Yes"
},
{
"_id" : ObjectId("60a4e92775e5de357057881f"),
"ProductName" : "Test2",
"ProductID" : ObjectId("60a4e92475e5de357057880d")
}
],
}
}
I have prepared a query for the below conditions and when I run the below query I should get the "UserId":1 documents but I got 0 records
condition 1: products should not be null
condition 2: ProductID should exist in the products array and should not be null
condition 3: IsDeliveryFailed should not be "Yes"
Based on the above user only one product got delivery failed(IsDeliveryFailed": "Yes") so when I run this query it should return "UserId":1 document. if both products "IsDeliveryFailed": "Yes" then
we should not get this user
Query
db.getCollection('bookings').find({
"$and": [
{ "BookingData.products": { $ne: [] } },
{ "BookingData.products": {"$elemMatch":{ "ProductID": { "$exists": true ,$ne: null } }} },
{ "BookingData.products": {"$elemMatch":{ "IsDeliveryFailed": { $ne: 'Yes' } }} }
]
})
Could someone please tell me the issue on the above query or please help me to prepare a query for the above condition?
I think you can do it with aggregations
db.collection.aggregate([
{
$match: {
"BookingData.products": { "$exists": true }
}
},
{
$set: {
"BookingData.products": {
"$filter": {
"input": "$BookingData.products",
"cond": {
$and: [
{ $ne: [ "$$this.ProductID", undefined ] },
{ $ne: [ "$$this._id", null ] },
{ $ne: [ "$$this.IsDeliveryFailed", "Yes" ] }
]
}
}
}
}
},
{
$match: {
$expr: {
$ne: [ "$BookingData.products", [] ]
}
}
}
])
Working Mongo playground
I want new field "isActive" inside modifierStatus sub document which is coming from modifieritems collection.
modifieritems collection :
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0
}
favoritedrinks collection :
{
"alcoholName" : "whiskey",
"modifierList" : [{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
}]
}
my query is :
db.getCollection('favoritedrinks').aggregate([
{ "$sort": { "alcoholName": 1 } },
{"$lookup": {
"from": "modifieritems",
localField: 'modifierList.modifierId',
foreignField: '_id',
as: 'modifier'
}},
{
$project:{
"alcoholName" : "$alcoholName",
"modifierStatus":"$modifier",
}
},
]);
But my expected result :
{
"alcoholName" : "Whiskey",
"modifierStatus" : [
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1,
},
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0,
}
]
}
anyone please help me?
Try this query :
Update with new requirement :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
"$lookup": {
"from": "modifieritems",
localField: "modifierList.modifierId",
foreignField: "_id",
as: "modifierStatus"
}
},
{
$addFields: {
modifierStatus: {
$map: {
input: "$modifierList",
as: "m",
in: {
$mergeObjects: [
{
$arrayElemAt: [ /** As filter would only get one object (cause you'll have only one matching doc in modifieritems coll for each "modifierList.modifierId", So getting first element out of array, else you need to take this array into an object & merge that field to particular object of 'modifierList') */
{
$filter: {
input: "$modifierStatus",
cond: {
$eq: [
"$$this._id",
"$$m.modifierId"
]
}
}
},
0
]
},
"$$m"
]
}
}
}
}
},
{
$project: {
modifierStatus: 1,
alcoholName: 1,
_id: 0
}
}
])
Test : MongoDB-Playground
Old :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
$lookup: {
from: "modifieritems",
let: {
id: "$modifierList.modifierId"
},
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$id"] } }
},
/** Adding a new field modifierId(taken from _id field of modifieritems doc)
* to each matched document from modifieritems coll */
{
$addFields: {
modifierId: "$_id"
}
}
],
as: "modifierStatus"
}
},
/** By mentioning 0 to particular fields to remove them & retain rest all other fields */
{
$project: {
modifierList: 0,
_id: 0
}
}
])
Test : MongoDB-Playground
When you want $project to include a field's current value while keeping the same field name, you need only specify :1. When you use "$field" you are explicitly setting the value, which will overwrite any existing value.
Try making your projection:
{
$project:{
"alcoholName" : 1,
"modifier.isActive": 1,
"modifier.modifierName": 1
}
}
MongoDb User Collection
Think that you are User 1. In the inbox page, I want to get the conversation's last message. I may sent the last message or receive the last message from a user. The last message will be shown in inbox like this:
Query Result Shold Be Like This
[
{
"_id": "user2",
"username": "user2",
"lastMessage": "3"
},
{
"_id": "user3",
"username": "user3",
"lastMessage": "2"
}
]
User 1 Document on MongoDb
{
"_id" : ObjectId("user1"),
"username" : "user1",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user2")
}
},
"message" : "1",
"received_at" : ISODate("2019-04-27")
},
{
"from" : {
"user" : {
"id" : ObjectId("user3")
}
},
"message" : "2",
"received_at" : ISODate("2019-05-1")
}
]
}
User 2 Document on MongoDb
{
"_id" : ObjectId("user2"),
"username" : "user2",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user1")
}
},
"message" : "3",
"received_at" : ISODate("2019-04-29")
}
]
}
User 3 Document on MongoDb
{
"_id" : ObjectId("user3"),
"username" : "user3",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user1")
}
},
"message" : "4",
"received_at" : ISODate("2019-04-30")
}
]
}
What query I have to use for this problem ?
You can use below aggregation:
db.col.aggregate([
{
$unwind: "$inbox"
},
{
$addFields: {
participants: [ "$_id", "$inbox.from.user.id" ]
}
},
{
$match: { participants: "user1" }
},
{
$addFields: {
participants: {
$filter: {
input: "$participants",
cond: {
$ne: [ "$$this", "user1" ]
}
}
}
}
},
{
$unwind: "$participants"
},
{
$sort: { "inbox.received_at": -1 }
},
{
$group: {
_id: "$participants",
lastMessage: { $first: "$inbox.message" }
}
}
])
The challenge here is that you need to analyse an array which might contain for instance [user1, user2] or [user2, user1] and both should be considered as the same grouping key.
To do that you can introduce participants array in order to filter out all the messages that do not belong to user1 and then remove user1 from that array (using $filter) so that you can group by second user.
The point is that you run $unwind to get single document per message and then $sort them so that you can run $group with $first to get the most recent one
Mongo Playground
I get a document from a mongodb which contains an array with comments for that document. In the comment is the _id of the user which wrote the comment.
I now need to get the username based on the _id of the user, but I'm running into several problems.
I have the following code which, obviously, doesn't work, but I hope it can give you an idea of what I'm trying to accomplish.
//MORE CODE... (No need to show this here, just a promise, some try catch and so on)
let article = await Article.findOne({_id:articleid})
for(var i = 0; i<=article.comment.length-1; i++){
User.findOne({_id:article.comment[i].user}).then((user)=>{
article.comment[i].username = user.username
})
}
return resolve(article)
I looked up several documentations but wasn't able to find a working solution. I tried using Promise.all, played around with a lot of async, await, tried to add a counter into the for-loop and resolve the promise after the loop finished but nothing worked so far.
This is what the article looks like in my db
{
"_id" : ObjectId("5c18c1cbc47e5e29d42e4b0e"),
"completed" : false,
"completedAt" : null,
"comment" : [
{
"_id" : ObjectId("5c18c95e328c8319ac07d817"),
"comment" : "This is a comment",
"rating" : [ ],
"user" : ObjectId("5c18b76e73236d2168eda2b4")
},
{
"_id" : ObjectId("5c18fb578de5741f20a4e2bd"),
"comment" : "Another comment",
"rating" : [ ],
"user" : ObjectId("5c18b76e73236d2168eda2b4")
}
]
}
I'm rather new to nodejs and mongodb aswell so I hope you can help a newbie like me.
Thank you for your Help
There are serveral approaches you can use here based on your convenience
Using async await
let article = await Article.findOne({ _id: articleid }).lean().exec()
await Promise.all(
article.comment.map(async(obj) => {
const user = await User.findOne({ _id: obj.user })
obj.username = user.username
})
)
console.log(article)
Using $lookup aggregation 3.6
Since mongodb has its own powerfull $lookup aggregation operator to join multiple collection and probably the better approach without any iteration
Article.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(articleid) }},
{ "$unwind": "$comment" },
{ "$lookup": {
"from": "users",
"let": { "userId": "$comment.user" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$$userId", "$_id"] }}}
],
"as": "comment.user"
}},
{ "$unwind": "$comment.user" },
{ "$group": {
"_id": "$_id",
"comment": { "$push": "$comment" },
"completed": { "$first": "$completed" },
"completedAt": { "$first": "$completedAt" }
}}
])
Using $lookup aggregation 3.4
Article.aggregate([
{ "$match": { "_id": mongoose.Types.ObjectId(articleid) }},
{ "$unwind": "$comment" },
{ "$lookup": {
"from": "users",
"localField": "comment.user",
"foreignField": "_id",
"as": "comment.user"
}}
{ "$unwind": "$comment.user" },
{ "$group": {
"_id": "$_id",
"comment": { "$push": "$comment" },
"completed": { "$first": "$completed" },
"completedAt": { "$first": "$completedAt" }
}}
])
You can try like the following way
const d = {
"_id" : ObjectId("5c18c1cbc47e5e29d42e4b0e"),
"completed" : false,
"completedAt" : null,
"comment" : [
{
"_id" : ObjectId("5c18c95e328c8319ac07d817"),
"comment" : "This is a comment",
"rating" : [ ],
"user" : ObjectId("5c18b76e73236d2168eda2b4")
},
{
"_id" : ObjectId("5c18fb578de5741f20a4e2bd"),
"comment" : "Another comment",
"rating" : [ ],
"user" : ObjectId("5c18b76e73236d2168eda2b4")
}
]
}
d.comment.forEach( async (obj, index) => {
await new Promise((res) => {
obj.counter = index;
res();
})
});
console.log(d);
For reference please take a look on following link
Asycn/Await using forEach
This is my appointment collection:
{ _id: ObjectId("518ee0bc9be1909012000002"), date: ISODate("2013-05-13T22:00:00Z"), patient:ObjectId("518ee0bc9be1909012000002") }
{ _id: ObjectId("518ee0bc9be1909012000002"), date: ISODate("2013-05-13T22:00:00Z"), patient:ObjectId("518ee0bc9be1909012000002") }
{ _id: ObjectId("518ee0bc9be1909012000002"), date: ISODate("2013-05-13T22:00:00Z"), patient:ObjectId("518ee0bc9be1909012000002") }
I used aggregate to get the following result
{date: ISODate("2013-05-13T22:00:00Z"),
patients:[ObjectId("518ee0bc9be1909012000002"),ObjectId("518ee0bc9be1909012000002"),ObjectId("518ee0bc9be1909012000002")] }
like this:
Appointments.aggregate([
{$group: {_id: '$date', patients: {$push: '$patient'}}},
{$project: {date: '$_id', patients: 1, _id: 0}}
], ...)
How can I populate the patient document
I trued this but it doesn't work ... Appointments.find({}).populate("patient").aggregate....
In other words, can i use populate and aggregate at the same statement
any help please
With the latest version of mongoose (mongoose >= 3.6), you can but it requires a second query, and using populate differently. After your aggregation, do this:
Patients.populate(result, {path: "patient"}, callback);
See more at the Mongoose API and the Mongoose docs.
Edit: Looks like there's a new way to do it in the latest Mongoose API (see the above answer here: https://stackoverflow.com/a/23142503/293492)
Old answer below
You can use $lookup which is similar to populate.
In an unrelated example, I use $match to query for records and $lookup to populate a foreign model as a sub-property of these records:
Invite.aggregate(
{ $match: {interview: req.params.interview}},
{ $lookup: {from: 'users', localField: 'email', foreignField: 'email', as: 'user'} }
).exec( function (err, invites) {
if (err) {
next(err);
}
res.json(invites);
}
);
You have to do it in two, not in one statement.
In async await scenario, make sure await until populate.
const appointments = await Appointments.aggregate([...]);
await Patients.populate(appointments, {path: "patient"});
return appointments;
or (if you want to limit)
await Patients.populate(appointments, {path: "patient", select: {_id: 1, fullname: 1}});
You can do it in one query like this:
Appointments.aggregate([{
$group: {
_id: '$date',
patients: {
$push: '$patient'
}
}
},
{
$project: {
date: '$_id',
patients: 1,
_id: 0
}
},
{
$lookup: {
from: "patients",
localField: "patient",
foreignField: "_id",
as: "patient_doc"
}
}
])
populate basically uses $lookup under the hood.
in this case no need for a second query.
for more details check MongoDB aggregation lookup
Perform a Join with $lookup
A collection orders contains the following documents:
{ "_id" : 1, "item" : "abc", "price" : 12, "quantity" : 2 }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1 }
{ "_id" : 3 }
Another collection inventory contains the following documents:
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
{ "_id" : 2, "sku" : "def", description: "product 2", "instock" : 80 }
{ "_id" : 3, "sku" : "ijk", description: "product 3", "instock" : 60 }
{ "_id" : 4, "sku" : "jkl", description: "product 4", "instock" : 70 }
{ "_id" : 5, "sku": null, description: "Incomplete" }
{ "_id" : 6 }
The following aggregation operation on the orders collection joins the documents from orders with the documents from the inventory collection using the fields item from the orders collection and the sku field from the inventory collection:
db.orders.aggregate([
{
$lookup:
{
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
])
The operation returns the following documents:
{
"_id" : 1,
"item" : "abc",
"price" : 12,
"quantity" : 2,
"inventory_docs" : [
{ "_id" : 1, "sku" : "abc", description: "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "jkl",
"price" : 20,
"quantity" : 1,
"inventory_docs" : [
{ "_id" : 4, "sku" : "jkl", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"inventory_docs" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}
Reference $lookup
Short answer:
You can't.
Long answer:
In the Aggregation Framework, the returned fields are built by you, and you're able to "rename" document properties.
What this means is that Mongoose can't identify that your referenced documents will be available in the final result.
The best thing you can do in such a situation is populate the field you want after the query has returned. Yes, that would result in two DB calls, but it's what MongoDB allows us to do.
Somewhat like this:
Appointments.aggregate([ ... ], function( e, result ) {
if ( e ) return;
// You would probably have to do some loop here, as probably 'result' is array
Patients.findOneById( result.patient, function( e, patient ) {
if ( e ) return;
result.patient = patient;
});
});
domain.Farm.aggregate({
$match: {
"_id": mongoose.Types.ObjectId(farmId)
}
}, {
$unwind: "$SelfAssessment"
}, {
$match: {
"SelfAssessment.questionCategoryID": QuesCategoryId,
"SelfAssessment.questionID": quesId
}
},function(err, docs) {
var options = {
path: 'SelfAssessment.actions',
model: 'FarmAction'
};
domain.Farm.populate(docs, options, function (err, projects) {
callback(err,projects);
});
});
results i got action model populate
{ "error": false, "object": [
{
"_id": "57750cf6197f0b5137d259a0",
"createdAt": "2016-06-30T12:13:42.299Z",
"updatedAt": "2016-06-30T12:13:42.299Z",
"farmName": "abb",
"userId": "57750ce2197f0b5137d2599e",
"SelfAssessment": {
"questionName": "Aquatic biodiversity",
"questionID": "3kGTBsESPeYQoA8ae2Ocoy",
"questionCategoryID": "5aBe7kuYWIEoyqWCWcAEe0",
"question": "Waterways protected from nutrient runoff and stock access through fencing, buffer strips and off stream watering points",
"questionImage": "http://images.contentful.com/vkfoa0gk73be/4pGLv16BziYYSe2ageCK04/6a04041ab3344ec18fb2ecaba3bb26d5/thumb1_home.png",
"_id": "57750cf6197f0b5137d259a1",
"actions": [
{
"_id": "577512c6af3a87543932e675",
"createdAt": "2016-06-30T12:38:30.314Z",
"updatedAt": "2016-06-30T12:38:30.314Z",
"__v": 0,
"Evidence": [],
"setReminder": "",
"description": "sdsdsd",
"priority": "High",
"created": "2016-06-30T12:38:30.312Z",
"actionTitle": "sdsd"
}
],
"answer": "Relevant"
},
"locations": []
} ], "message": "", "extendedMessage": "", "timeStamp": 1467351827979 }
I see that there are many answers, I am new to mongoldb and I would like to share my answer too.
I am using aggregate function along with lookup to populate the patients.
To make it easy to read I have changed the names of the collections and fields.
Hope it's helpful.
DB:
db={
"appointmentCol": [
{
_id: ObjectId("518ee0bc9be1909012000001"),
date: ISODate("2013-05-13T22:00:00Z"),
patientId: ObjectId("518ee0bc9be1909012000001")
},
{
_id: ObjectId("518ee0bc9be1909012000002"),
date: ISODate("2013-05-13T22:00:00Z"),
patientId: ObjectId("518ee0bc9be1909012000002")
},
{
_id: ObjectId("518ee0bc9be1909012000003"),
date: ISODate("2013-05-13T22:00:00Z"),
patientId: ObjectId("518ee0bc9be1909012000003")
}
],
"patientCol": [
{
"_id": ObjectId("518ee0bc9be1909012000001"),
"name": "P1"
},
{
"_id": ObjectId("518ee0bc9be1909012000002"),
"name": "P2"
},
{
"_id": ObjectId("518ee0bc9be1909012000003"),
"name": "P3"
},
]
}
Aggregate Query using lookup:
db.appointmentCol.aggregate([
{
"$lookup": {
"from": "patientCol",
"localField": "patientId",
"foreignField": "_id",
"as": "patient"
}
}
])
Output:
[
{
"_id": ObjectId("518ee0bc9be1909012000001"),
"date": ISODate("2013-05-13T22:00:00Z"),
"patient": [
{
"_id": ObjectId("518ee0bc9be1909012000001"),
"name": "P1"
}
],
"patientId": ObjectId("518ee0bc9be1909012000001")
},
{
"_id": ObjectId("518ee0bc9be1909012000002"),
"date": ISODate("2013-05-13T22:00:00Z"),
"patient": [
{
"_id": ObjectId("518ee0bc9be1909012000002"),
"name": "P2"
}
],
"patientId": ObjectId("518ee0bc9be1909012000002")
},
{
"_id": ObjectId("518ee0bc9be1909012000003"),
"date": ISODate("2013-05-13T22:00:00Z"),
"patient": [
{
"_id": ObjectId("518ee0bc9be1909012000003"),
"name": "P3"
}
],
"patientId": ObjectId("518ee0bc9be1909012000003")
}
]
Playground:
mongoplayground.net
I used lookup instead, and it worked well. See the code snipped below.
Post.aggregate([
{
$group: {
// Each `_id` must be unique, so if there are multiple
// posts with the same category, MongoDB will increment `count`.
_id: '$category',
count: { $sum: 1 }
}
},
//from: is collection name in MongoDB, localField are primary and foreign keys in Model.
{$lookup: {from: 'categories', localField: '_id', foreignField:'_id', as: 'category'}}
]).then(categoryCount => {
console.log(categoryCount);
let json = [];
categoryCount.forEach(cat => {
console.log(json);
});