findOneAndUpdate is not working inside array - node.js

I'm trying to update the document in the MongoDB collection but it's not working for me. Here is the function all fields come to the backend.
{
"_id" : ObjectId("59a6b381c13a090c70fc21b6"),
"processNumber" : "FEE 082517",
"System" : "abc",
"TaxAmount" : 0,
"TaxPercent" : 0,
"Currency" : "USD",
"ProcessData" : [
{
"_id" : ObjectId("59ee2873b1621419a03fba6c"),
"KDSID" : "1db1d4b8-61bc-45eb-bf6d-15af1e391df5"
},
{
"_id" : ObjectId("59ee2873b1621419a03fba6d"),
"KDSID" : "aa9ccaf3-a638-4013-afdc-ccf0a39361e8"
},
{
"_id" : ObjectId("59ee2873b1621419a03fba6e"),
"KDSID" : "4c5e32a7-e2fb-4fe9-998f-e22602e46dba"
}
{
"Name" : "2017 Calc.xlsx",
"FileID" : "59ee2873b1621419a03fb9b7",
"_id" : ObjectId("59ee2873b1621419a03fba75")
}
]
}
Query:
db.process.findOneAndUpdate(
{ 'ProcessData._id': ObjectId("59ee2873b1621419a03fba75"),'ProcessData.FileID': { '$exists': true, '$ne': null }},
{ $set: { 'ProcessData.$.IsFailed': "Yes" } }
)
When I run the above query IsFailed is not updating. can you please advise?
I have tried in node and MongoDB and it's not working.
If ProcessData._id matches with the given id and ProcessData.FileID exist we have to set IsFailed Yes

here's my version with $elemMatch
db.process.updateOne(
{
ProcessData: {
$elemMatch: {
_id: ObjectId("59ee2873b1621419a03fba75"),
FileID: {
$exists: true,
$ne: null
}
}
}
},
{
$set: { "ProcessData.$.IsFaild": "Yes" }
})
https://mongoplayground.net/p/3LBlw9kA6Gl

You need to use arrayFilter when you modify the array.
db.collectionName.updateOne(
{
'ProcessData._id': ObjectId("59ee2873b1621419a03fba75"),
'ProcessData.FileID': { '$exists': true, '$ne': null }
},
{
$set:{"ProcessData.$[p].isFailed": "Yes"}},
{
arrayFilters:[{"p._id":ObjectId("59ee2873b1621419a03fba75")}]
}
)

You're querying through an array of embedded documents, and hence by using elemMatch to find the specific id and perform needed operation on that document should be implemented for example as below:
db.process.updateOne(
{
ProcessData: {
$elemMatch: {
_id:
ObjectId("59ee2873b1621419a03fba75"),
}
}
},
{
"ProcessData.FileID": {
$exists: true,
$ne: null
}
},
{
$set: {
"ProcessData.$.IsFailed": "Yes"
}
})

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

MongoDB aggregation: FieldPath may not start with $

This MongoDB aggregation is failing:
Attendance.aggregate([
{ $match: { cohort_id: cohort_id} },
{ $unwind: "$absences" },
{
$group: {
_id: {
term: "$absences.term",
$function:
{
body: function (day) {
return day.getDay();
},
args: ["$absences.formatted_date.day"],
lang: "js",
},
},
count: { $sum: 1 },
},
},
{ $sort: { count: 1 } },
])
with this error:
uncaught exception: Error: command failed: {
"ok" : 0,
"errmsg" : "FieldPath field names may not start with '$'. Consider using $getField or $setField.",
"code" : 16410,
"codeName" : "Location16410"
} with original command request: {
"aggregate" : "attendances",
"pipeline" : [
{
"$match" : {
"cohort_id" : "61858e13dc5e0d1ce0238abd"
}
},
{
"$unwind" : "$absences"
},
{
"$group" : {
"_id" : {
"term" : "$absences.term",
"$function" : {
"body" : function (day) { return day.getDay(); },
"args" : [
"$absences.formatted_date.day"
],
"lang" : "js"
}
},
"count" : {
"$sum" : 1
}
}
},
{
"$sort" : {
"count" : 1
}
}
],
"cursor" : {
},
"lsid" : {
"id" : UUID("b4505aa0-e65e-46cd-8e31-03e4ecdbfe3b")
}
}
...
Not the most helpful error message.
Where am I referencing a field name wrong? Looks like it's expecting a field name without $ somewhere, but I can't seem to find where.
I've seen similar posts about this error, but they generally have to do with $project and $sort which does not seem to be the problem here
Thank you!
It considers $function as field name. I think it should be like this:
{
$group: {
_id: {
term: "$absences.term",
day: {
$function: {
body: function (day) {
return day.getDay();
},
args: ["$absences.formatted_date.day"],
lang: "js",
},
},
count: { $sum: 1 },
},
}
Is this a school homework? day.getDay() sounds to be a very simple function which should be available native in MongoDB Query Language.
Found a solution that's simpler and that works:
Attendance.aggregate([
{ $match: { cohort_id: cohort_id} },
{ $unwind: "$absences" },
{
$group: {
_id: {
term: "$absences.term",
day: {
$dayOfWeek: "$absences.formatted_date.day"
},
},
count: { $sum: 1 },
},
},
{ $sort: { count: 1 } },
])

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

How to update multiple documents?

I want to update multiple documents.
My current Document,
Document Account
{
"_id" : "5cbd96aca1a6363473d4g8745",
"contact" : [
"5cbd96aca1a6363473d4a968",
]
},
{
"_id" : "5cbd96aca1a6363473d4g8746",
"contact" : [
"5cbd96aca1a6363473d4z7632",
]
}
I need below output,
update contact array with different _id.
Document Account
{
"_id" : "5cbd96aca1a6363473d4g8745",
"contact" : [
"5c98833f98770728a7047f1a",
"5cbd96aca1a6363473d4a968",
]
},
{
"_id" : "5cbd96aca1a6363473d4g8746",
"contact" : [
"5caddf78b8c0645402090536",
"5cbd96aca1a6363473d4z763",
]
}
Use $addToSet or $push to push id with bulk update.
You can use update with upsert. It will update the doc if exist and if not then it will create new one.
for example:
//Make a obj to set
var contacts = {
id: req.body.id,
contactIds: req.body.contactIds,
};
req.app.db.models.ModelsName.update(
{
//if you want multiple fields to be update
$and: [{ id: contacts.id }, { contactIds: { $in: contacts.contactIds } }]
},
//Set the above obj
{ $set: contacts },
{ upsert: true },
(err, result) => {
if (err) {
console.log(err.message)
}
console.log("Updated successfully")
})
This is just a reference. Modify accordingly your use.
You can use Bulk.find.update() method to update all matching documents.
example:
var bulk = db.items.initializeUnorderedBulkOp();
bulk.find( { status: "D" } ).update( { $set: { status: "I", points: "0" } } );
bulk.find( { item: null } ).update( { $set: { item: "TBD" } } );
bulk.execute();

Resources