MongoDB: Updating the path 'Y' would create a conflict at 'Y' - node.js

I have the following query being executed:
const myData = await this.findOneAndUpdate({
myId,
color,
}, {
$setOnInsert: {
myId,
color,
date: now.format('YYYY-MM-DD'),
counter: myObj.counter - amount,
},
$inc: {
counter: -amount
},
}, {
new: true,
upsert: true,
});
I get the error:
"Updating the path 'count' would create a conflict at 'count'"
First I thought the error was happening because of the version of mongoose, but I don't think that is the case.
Now I understand this is happening because I have color in both $setOnInsert and $inc, but I don't understand why.
Also: This code works on MongoDB 3.4.24 Community but does NOT WORK on MongoDB 5.0.11 Community
So my questions are:
why this error is happening exactly? Could this be a BUG?
Why this works in older version of MongoDB?
What would be the best approach to refactor this?

You are getting the above error from MongoDB, because of the way $inc works, with upsert: true, new: true, $inc will insert a new document. Check this playground.
In your case, you have $setOnInsert and $inc, in case your matching document is not found, both the operators will try to set the value of key counter, which will create a conflict, hence you see the error. To fix it, you can use the pipeline form of updates, something like this:
const myData = await this.findOneAndUpdate({
myId,
color,
}, [
{
$set: {
myId,
color,
date: {
$cond: {
if: {$eq: ["$$ROOT", null]},
then: now.format('YYYY-MM-DD'),
else: "$$ROOT.date"
}
},
counter: {
$cond: {
if: {$eq: ["$$ROOT", null]},
then: myObj.counter - amount,
else: {$substract: ["$$ROOT.counter", amount]}
}
}
}
}
], {
new: true,
upsert: true,
});

Related

using updateOne inside a map, setting the filter to the unique id , $set is to the document and upsert is true. Even then new document is created

here is my code in the controller from where i am getting the records from my google calendar API and then passing that data to this function and the code inside the function which inserts the document (records) looks like this as below:
Holiday.bulkWrite(
holidays.map((holiday) => ({
updateOne: {
filter: { holidayId: holiday.id },
update: { $set: holiday },
upsert: true,
},
}))
)
It's hard to tell what exactly the issue is because it is not Mongo related but code related, from what it seems you are just using the wrong field for the filter.
holiday.id is null, and we can see that the "inserted" documents do not have such field. You are basically executing the following update:
db.collection.update({
holidayId: null
},
{
"$set": {
holidayId: "123"
... other fields
}
},
{
"upsert": true
})
I believe this simple fix would solve your issue, change .id To .holidayId:
Holiday.bulkWrite(
holidays.map((holiday) => ({
updateOne: {
filter: { holidayId: holiday.holidayId },
update: { $set: holiday },
upsert: true,
},
}))
)

Mongoose: Update/Insert in nested subdocument

I've seen quite a few examples here in stackoverflow, but none of them have been useful to me. Maybe my scheme is wrong?
I need to insert new records into a nested document using Mongoose (I would like to add within the "history" array). If the document already exists, I must only update it, if it does not exist, a new document must be added. I have the following scheme:
let equipment_json = {
controls: [{
_id: con.Schema.Types.ObjectId,
name: String,
history: [{
_id: con.Schema.Types.ObjectId,
new: Boolean,
}]
}]
};
let equipment_schema = new con.Schema(equipment_json);
let Equipment = con.mongoose.model('Equipment', Equipment_schema);
This code should perform the update:
Equipment.update({
'_id': object_equipment.id_equipment,
'controls._id': object_equipment.id_control_type
},{
$set: {
'controls.$.history.$': {
new: true
}
}
},
{
upsert: true, setDefaultsOnInsert: true
},
function (err, doc ) {
console.log( doc );
})
Before using update() I used find() to check what it finds according to the criteria. Using find(), it returns the document, however, when I want to use update() it does not add to the array "controls", the "new": "true". I tried as much with $set as with $push.
It's only necessary to modify the following code:
'controls.$.history.$': {
new: true
}
by
'controls.$.history': {
new: true
}

Mongoose findByIdAndUpdate() with select option [duplicate]

Using Mongoose in Nodejs you can return some fields using find.
eg.
User.findOne({_id:'132324'}, {first_name:1, last_name:1}).exec...
but I can't seem to figure out how to return certain fields using findOneAndUpdate.
User.findOneAndUpdate({_id:'132324'}, {$set : {..bla bla}}, {first_name:1, last_name:1}).exec....
Has anyone achieved this before?
I can't find it in the documentation.
From the manual, the options argument needs a "fields" key in it since there are other details such as "upsert" and "new" where this applies. In your case you also want the "new" option:
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"fields": { "first_name":1, "last_name": 1 },
"new": true
}
).exec(...)
Alternately you may use .select()
User.select({ "first_name": 1, "last_name": 1 }).findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{ "new": true }
).exec(...)
Noting that without "new": true the document returned is in the state before the modification of the update was processed. Some times this is what you mean, but most of the time you really want the modified document.
It seems the syntax for findByIdAndUpdate has changed a little.
Its takes the form of Model.findByIdAndUpdate(query, update, options, (optional callback))
According to the docs, to specify which fields are returned you have to specify them in the options parameter. so, using the above example it would be:
User.findOneAndUpdate(
{id}, //query
{ $set: { "fieldToBeChanged": "update" } }, //update
{new:true, select: "fieldIWant anotherFieldIWant"}, //options
})
The new:true options is not necessary. If omitted mongoose defaults to returning the document before the updates were applied. Set to true and it will return the document after the updates have been made.
We can exclude any field while using mongoose update function findByIdAndUpdate with the help of select function,please have a look at the following code it will exclude password and __v from the Update response
User.findByIdAndUpdate({ _id: req.userData.UserId },req.body,{new: true}).select({Password: 0, __v: 0 })
.exec()
.then(result => {})
Try this:
User.findOneAndUpdate({_id:'132324'}, {update: '1235'}, {new: true}).select({ _id: 0 })
The select() will exclude the _id field from the result.
For MongoDb version 3.2+
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"projected": { "first_name":1, "last_name": 1 },
"returnNewDocument": true
}
)...
For MongoDB version: 4.0.2, mongoose version: 5.13.7:
userModel.findOneAndUpdate({_id:'132324'},{$set:
{"first_name":"user","last_name":"one"}},
{new:true,select:{first_name:1, last_name:1}},(err,data)=>{}
Use option {new: true} in findOneAndUpdate (default: false)

mongodb remove document if array count zero after $pull in a single query

I have a requirement where my comments schema looks like the following
{
"_id": 1,
"comments": [
{ "userId": "123", "comment": "nice" },
{ "userId": "124", "comment": "super"}
]
}
I would like to pull the elements based on the userId field.
I am doing the following query
comments.update({},{$pull:{comments:{userId:"123"}}})
My requirement is that if the array length became zero after the pull operator I need to remove the entire document for some reason.Is there a away to do this in a single query?
PS:I am using the mongodb driver.Not the mongoose
If I'm reading your question right, after the $pull, if the comments array is empty (zero length), then remove the document ({ _id: '', comments: [] }).
This should remove all documents where the comments array exists and is empty:
comments.remove({ comments: { $exists: true, $size: 0 } })
I had a similar requirement and used this (using mongoose though):
await Attributes.update({}, { $pull: { values: { id: { $in: valueIds } } } }, { multi: true })
await Attributes.remove({ values: { $exists: true, $size: 0 } })
Not sure if it's possible to do this in one operation or not.
You can use middlewares for this.
http://mongoosejs.com/docs/middleware.html
Write a pre/post update method in mongodb to check your condition.

Mongoose select fields to return from findOneAndUpdate

Using Mongoose in Nodejs you can return some fields using find.
eg.
User.findOne({_id:'132324'}, {first_name:1, last_name:1}).exec...
but I can't seem to figure out how to return certain fields using findOneAndUpdate.
User.findOneAndUpdate({_id:'132324'}, {$set : {..bla bla}}, {first_name:1, last_name:1}).exec....
Has anyone achieved this before?
I can't find it in the documentation.
From the manual, the options argument needs a "fields" key in it since there are other details such as "upsert" and "new" where this applies. In your case you also want the "new" option:
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"fields": { "first_name":1, "last_name": 1 },
"new": true
}
).exec(...)
Alternately you may use .select()
User.select({ "first_name": 1, "last_name": 1 }).findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{ "new": true }
).exec(...)
Noting that without "new": true the document returned is in the state before the modification of the update was processed. Some times this is what you mean, but most of the time you really want the modified document.
It seems the syntax for findByIdAndUpdate has changed a little.
Its takes the form of Model.findByIdAndUpdate(query, update, options, (optional callback))
According to the docs, to specify which fields are returned you have to specify them in the options parameter. so, using the above example it would be:
User.findOneAndUpdate(
{id}, //query
{ $set: { "fieldToBeChanged": "update" } }, //update
{new:true, select: "fieldIWant anotherFieldIWant"}, //options
})
The new:true options is not necessary. If omitted mongoose defaults to returning the document before the updates were applied. Set to true and it will return the document after the updates have been made.
We can exclude any field while using mongoose update function findByIdAndUpdate with the help of select function,please have a look at the following code it will exclude password and __v from the Update response
User.findByIdAndUpdate({ _id: req.userData.UserId },req.body,{new: true}).select({Password: 0, __v: 0 })
.exec()
.then(result => {})
Try this:
User.findOneAndUpdate({_id:'132324'}, {update: '1235'}, {new: true}).select({ _id: 0 })
The select() will exclude the _id field from the result.
For MongoDb version 3.2+
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"projected": { "first_name":1, "last_name": 1 },
"returnNewDocument": true
}
)...
For MongoDB version: 4.0.2, mongoose version: 5.13.7:
userModel.findOneAndUpdate({_id:'132324'},{$set:
{"first_name":"user","last_name":"one"}},
{new:true,select:{first_name:1, last_name:1}},(err,data)=>{}
Use option {new: true} in findOneAndUpdate (default: false)

Resources