Mongoose : Find and Update Multiple Nested Documents - node.js

My Documents are as follows.
{
"order_id" : "1",
"payment_status" : false,
"items" : [
{
"item_id" : 1,
"payment_status" : false,
},
{
"item_id" : 2,
"payment_status" : false,
},
{
"item_id" : 3,
"payment_status" : false,
},
]
}
I need to update the fields payment_status for {"order_id":1} and {"item_id" : 1} and {"item_id" : 3}. Also, I need to update the same in bulk for matching conditions. Is this possible in mongoose?

You want to be using $arrayFilters like so:
db.collection.updateMany({
"order_id": "1"
},
{
"$set": {
"items.$[item].payment_status": true
}
},
{
arrayFilters: [
{
"item.item_id": {
$in: [
1,
3
]
}
}
]
})
Mongo Playground

Related

Mongoose, update values in array inside an object in an array

How can i update values in array inside an object in an array.
{
"_id" : ObjectId("63c7ca9584535c160ee4aaed"),
"status" : "REJECTED",
"steps" : [
{
"_id" : ObjectId("63c7ca9884535c160ee4ab03"),
"status" : "REJECTED",
"stepUsers" : [
{
"_id" : ObjectId("63c7ca9884535c160ee4ab04"),
"status" : "REJECTED",
}
]
},
]
}
I tried to update using arrayFilters but that didn't work. Mongo throw an error MongoServerError: Found multiple array filters with the same top-level field name steps
Collection.updateOne({
_id: id
}, {
$set: {
"steps.$[steps].stepUsers.$[stepUsers].status": 'PENDING',
}
}, {
arrayFilters: [
{ "steps._id": step._id },
{ "steps.stepUsers._id": stepUser._id }
]
})
I need to update steps.stepUsers.status in the collection.
Try to change the arrayFilters: "steps.stepUsers._id" -> "stepUsers._id"
since arrayFilters is referencing the string inside the [], not the path to it.
Collection.updateOne({
_id: id
},
{
$set: {
"steps.$[steps].stepUsers.$[stepUsers].status": "PENDING",
}
},
{
arrayFilters: [
{
"steps._id": step._id
},
{
"stepUsers._id": stepUser._id
}
]
})
See how it works on the playground example

How do I update a field in embedded documents based on another array in MongoDB

I am trying to update an embedded document in MongoDB using mongoose in nodejs. The document is simplified and shown below (The names in friendList is assumed to be unique):
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList" : [
{
"name" : "Alex",
"flag" : false,
},
{
"name" : "Bob",
"flag" : false,
},
{
"name" : "Caleb",
"flag" : true,
},
{
"name" : "Debbie",
"flag" : false,
}
]
}
I would like to update this collection by:
accepting a Patch API with a request body containing a subset of friendList and
update the nested field flag.
For example, if I were to do a patch call from postman with the request body:
{
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
then I should expect my document in MongoDB to look like this:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Bob",
"flag":false
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
What I have tried on nodejs is updating the entire request body:
function updateUser(req){
User.findOneAndUpdate({'_id':req.params._id},req.body,{new:true});
}
which replaces the entire friendList array:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList":[
{
"name":"Alex",
"flag":true
},
{
"name":"Caleb",
"flag":false
},
{
"name":"Debbie",
"flag":false
}
]
}
I have also tried using array operators like $:
function updateUser(req){
User.findOneAndUpdate(
{'_id':req.params._id},
{$addToSet:{
"friendList":{
$each:req.body.friendList}
}
},
{new:true}
);
}
which gave me the output:
{
"_id" : ObjectId("5eb0617f3aec924ff42249cd"),
"friendList" : [
{
"name" : "Alex",
"flag" : false,
},
{
"name" : "Bob",
"flag" : false,
},
{
"name" : "Caleb",
"flag" : true,
},
{
"name" : "Debbie",
"flag" : false,
},
{
"name" : "Alex",
"flag" : true,
},
{
"name" : "Caleb",
"flag" : false,
},
]
}
which $addToSet considers both name and flag when making a comparison to check if the values exist in the array. It might work if I am able to intercept at this comparison phase such that only the name field is checked.
I have been exploring concepts like $[<identifier>] and arrayFilter but can't seem to make it work.
Simple $addToSet does not work, because your array is not ["Alex","Caleb","Debbie"]. Your array is
[
{name: "Alex", flag: true},
{name: "Caleb", flag: false},
{name: "Debbie", flag: false}
]
Element {name:"Alex", flag: true} is different to element {name: "Alex", flag: false}, that's the reason why your approach failed. I think you have to use aggregation pipeline, e.g. this one:
db.collection.aggregate([
{ $addFields: { newFriends: friendList } },
{
$set: {
friendList: {
$map: {
input: "$friendList",
in: {
name: "$$this.name",
flag: {
$cond: [
{ $eq: [{ $indexOfArray: ["$newFriends.name", "$$this.name"] }, - 1] },
"$$this.flag",
{ $arrayElemAt: [ "$newFriends.flag", { $indexOfArray: ["$newFriends.name", "$$this.name"] } ] }
]
}
}
}
}
}
},
{ $unset: "newFriends" }
])
Or if you like to work with index variable:
db.collection.aggregate([
{ $addFields: { newFriends: friendList } },
{
$set: {
friendList: {
$map: {
input: "$friendList",
in: {
$let: {
vars: { idx: { $indexOfArray: ["$newFriends.name", "$$this.name"] } },
in: {
name: "$$this.name",
flag: {
$cond: [
{ $eq: ["$$idx", - 1] },
"$$this.flag",
{ $arrayElemAt: ["$newFriends.flag", "$$idx"] }
]
}
}
}
}
}
}
}
},
{ $unset: "newFriends" }
])
Note, this will update only existing names. New names are not added to the array, your question is not clear in this regard. If you like to add also new elements then insert
{
$set: {
friendList: { $setUnion: ["$friendList", "$newFriends"] }
}
},
just before { $unset: "newFriends" }
The aggregation pipeline can be used in an update:
User.findOneAndUpdate(
{'_id':req.params._id},
[
{ $addFields: { newFriends: req.body.friendList } },
{
$set: { ...
}
]
);

How to do two different arrayFilters in the same mongodb update?

In the application im building I have two updates that I want to do in the same query. I want to find the subdocument with the matching task_id and update its priority. In the same call I want to increment all the subdocuments with a priority higher than 3. Is it possible to combine these two in the same query?
const project = await Project.updateOne(
// first search
{ _id: req.params.project_id },
{ $set: {'tasks.$[element].priority': req.body.priority, 'tasks.$[element].state': req.body.state }},
{ arrayFilters: [{ 'element._id': req.params.task_id }] }
// second search
{ _id: req.params.project_id },
{ $inc: {'tasks.$[element].priority': 1 }},
{ arrayFilters: [{ 'element.priority': { $gt: 3 } }] }
);
you must be used different identifiers for your arrayFilters. your identifier is element. your code must be like that:
const project = await Project.updateOne(
// first search
{_id: req.params.project_id},
{
$set: {'tasks.$[elementA].priority': req.body.priority, 'tasks.$[elementA].state': req.body.state},
$inc: {'tasks.$[elementB].priority': 1}
},
{
arrayFilters: [
{'elementA._id': req.params.task_id},
{'elementB.priority': {$gt: 3}}
]
},
)
NOTE: The identifier must begin with a lowercase letter and contain only alphanumeric characters (from MongoDB official website, link)
You can do it simultaneously by using two arrayFilters. Consider the below:
Current collection:
{
"_id" : 1,
"array1" : [
{
"k1" : 1,
"v1" : 100
},
{
"k1" : 2,
"v1" : 15
},
{
"k1" : 1,
"v1" : 100
}
],
"array2" : [
{
"k2" : 1,
"v2" : 10
},
{
"k2" : 2,
"v2" : 1000
},
{
"k2" : 1,
"v2" : 20
}
]
}
Query:
db.collection.update(
{ _id: 1 },
{ $set:
{
'array1.$[elem1].v1': 100,
'array2.$[elem2].v2': 1000
}
},
{ arrayFilters:
[
{'elem1.k1':1},
{'elem2.k2': 2}
],
multi: true
}
)
As you can see that, I have created two filtered positional operator (elem1 and elem2), with the help of arrayFilters option. I can used this to perform my updates.
Result:
{
"_id" : 1,
"array1" : [
{
"k1" : 1,
"v1" : 100
},
{
"k1" : 2,
"v1" : 15
},
{
"k1" : 1,
"v1" : 100
}
],
"array2" : [
{
"k2" : 1,
"v2" : 10
},
{
"k2" : 2,
"v2" : 1000
},
{
"k2" : 1,
"v2" : 20
}
]
}
You can see in the above updated collection that the k1 field in array1 with value 1, it's v1 field have been updated to 100 and the k2 field in array2 with value 2, it's v2 field have been updated to 100.
So in your case you need to do something like below:
updateOne(
{ _id: req.params.project_id},
{
$set: {
'tasks.$[elem1].priority': req.body.priority,
'tasks.$[elem1].state': req.body.state
},
$inc: {
'tasks.$[elem2].priority': 1
}
},
{
arrayFilters: [
{ 'elem1._id': req.params.task_id },
{ 'elem2.priority':
{ $gt: 3 }
}
]
}
)
I hope it's helpful.

how to use $filter in nested array in Mongo db?

I have following documents in Agriculture model
{
"_id" : ObjectId("5e5c9c0a0cfcdb1538406000"),
"agricultureProductSalesType" : [
"cash_crops",
"vegetable",
"fruit"
],
"flag" : false,
"agricultureDetail" : [
{
"production" : {
"plantCount" : 0,
"kg" : 0,
"muri" : 0,
"pathi" : 0
},
"sale" : {
"plantCount" : 0,
"kg" : 0,
"muri" : 0,
"pathi" : 0
},
"_id" : ObjectId("5e5c9c0a0cfcdb1538406001"),
"title" : "alaichi",
"agricultureProductionSalesType" : "cash_crops"
},
{
"production" : {
"plantCount" : 0,
"kg" : 40,
"muri" : 0,
"pathi" : 0
},
"sale" : {
"plantCount" : 0,
"kg" : 0,
"muri" : 0,
"pathi" : 0
},
"_id" : ObjectId("5e5c9c0a0cfcdb1538406002"),
"title" : "amriso",
"agricultureProductionSalesType" : "cash_crops"
}
],
"agricultureParent" : [
{
"area" : {
"ropani" : 10,
"aana" : 0,
"paisa" : 0
},
"_id" : ObjectId("5e5c9c0a0cfcdb1538406005"),
"title" : "cash_crops",
"income" : 50000,
"expense" : 6000
}
],
"house" : ObjectId("5e5c9c090cfcdb1538405fa9"),
"agricultureProductSales" : true,
"insecticides" : false,
"fertilizerUse" : true,
"seedNeed" : "local",
"__v" : 0
I want result from above document with condition if agricultureDetail.title is not empty or blank string AND agricultureDetail.production.plantcount equals zero or null or exists false AND agricultureDetail.production.kg equals zero or null or exists false AND (all remaining elements inside production zero or null or exists flase).
I tried $elemMatch as bellow:
$and: [
{ agricultureProductSales: true },
{ agricultureDetail: { $exists: true, $ne: [] } },
{
$or: [
{ agricultureDetail: { $elemMatch: { title: { $ne: "" } } } },
{
agricultureDetail: { $elemMatch: { title: { $exists: true } } }
}
]
},
{
$or: [
{
agricultureDetail: {
$elemMatch: { production: { $exists: true } }
}
},
{
agricultureDetail: {
$elemMatch: { production: { $ne: [] } }
}
}
]
},
{
$or: [
{
"agricultureDetail.production": {
$elemMatch: { plantCount: { $exists: false } }
}
},
{
"agricultureDetail.production": {
$elemMatch: { plantCount: { $eq: 0 } }
}
},
{
"agricultureDetail.production": {
$elemMatch: { plantCount: { $eq: null } }
}
}
]
}
]
But it reurns empty result. Any help? THankyou so much.
Breaking down this query a bit:
{ agricultureProductSales: true },
Selects only true values.
{ agricultureDetail: { $exists: true, $ne: [] } }
Is extraneous. Since you are testing fields of sub-documents within this array, those later tests could not possibly succeed if the array were empty or didn't exist.
{
$or: [
{ agricultureDetail: { $elemMatch: { title: { $ne: "" } } } },
{
agricultureDetail: { $elemMatch: { title: { $exists: true } } }
}
]
},
This tests to see if title either doesn't equal "" (which includes elements where the field doesn't exist) or if the title field exists. One of these is always true, so this $or will always match. If you wanted to match only documents that contain an element with a non-empty title, test to see if it is greater than "" - since the query operators are type-sensitive, this will fail to match any title that doesn't exist, doesn't contain a string, or contains the empty string.
"agricultureDetail.title": { $gt: "" }
Similarly with plantCount, if you were to test $gt: 0, that would match only documents that contain a plantCount that is numeric and greater than 0. What you want is the logical inverse of that, so:
"agricultureDetail.production.plantCount": {$not: {$gt: 0}}
In this case, that would match elements that do not contain a production field, or those that have an empty array for the production field.
An existence test for plantCount will eliminate both of those possibilities, so
"agricultureDetail.production.plantCount": {$exists:true, $not: {$gt: 0}}
As written, all of these are testing if any element in the array matches any of the criteria.
If your intent is to match a document that contains a single element that matches all of the criteria, you would collect them together in an $elemMatch of the agricultureDetail fields. So the final query could look something like:
db.collection.find({
agricultureProductSales: true,
agricultureDetail:{$elemMatch:{
title: {$gt: ""},
"production.plantCount": {$exists:true, $not: {$gt: 0}}
}}
})
Playground

Node : Mongoose Updating User Schema with arrays

Here is my User Schema
{
"user_collection":[
{
"_id":"xxx",
"name":"NAME",
"prescription":
{
"doctor_id":
[{
"_id":"xxx",
"medicine_id":"MEDICINE_ID",
}]
},
"meal":{
"meal_name":{
"start_time":"START_TIME",
"end_time":"END_TIME"
}
},
"created_at":"CREATED_AT",
"updted_at":"UPDATED_AT"
}
]
}
Note : _id given just for understanding
I need to insert document inside the prescription array. Here is the condition
If the new doctor_id is given, it should add in the prescription array like
{
"_id" : ObjectId("5813288eaa0f1231de477d92"),
"name" : "andrew",
"prescription" : [
{
"prescription" : [
{
"_id" : ObjectId("58143d484e26a229873b0528"),
"medicine_id" : "10011241343"
}
],
"_id" : ObjectId("58143d484e26a229873b0527"),
"doctor_id" : "5813221ace684e2b3f5f0a6d"
}
]
}
And if i given the doctor_id that already exists it should add like
{
"_id" : ObjectId("5813288eaa0f1231de477d92"),
"name" : "andrew",
"prescription" : [
{
"prescription" : [
{
"_id" : ObjectId("58143d484e26a229873b0528"),
"medicine_id" : "10011241343"
}
],
"prescription" : [
{
"_id" : ObjectId("58143d484e26a229873b0529"),
"medicine_id" : "10011241349"
}
],
"_id" : ObjectId("58143d484e26a229873b0527"),
"doctor_id" : "5813221ace684e2b3f5f0a6d"
}
]
}
What i have tried is
dbModel.user.update({
_id: req.body.user_id
}, {
$set: {
prescription: [ { "doctor_id" : req.body.doctor_id, "prescription" : [
{
"medicine_id" : req.body.medicine_id
}]} ],
}
}, {
upsert: true
}, function(err) {
if (err) {
res.status(202).json({
"success": "0",
"message": err
})
} else {
res.status(200).json({
"success": "1",
"message": "Prescription given successfully"
});
}
})
I don't know how to check whether the doctor_id already exists and if it does not exists it should add a new array, and if it exists it should add inside the existing arrays
Take a look in this answer.
But basically you can use the $ operator which identifies an element in an array.
You can see here some mongodb array operators.

Resources