Updating the path 'x' would create a conflict at 'x' - mongodb

This error happens when I tried to update upsert item:
Updating the path 'x' would create a conflict at 'x'

Field should appear either in $set, or in $setOnInsert. Not in both.

I had the same problem while performing an update query using PyMongo.
I was trying to do:
> db.people.update( {'name':'lmn'}, { $inc : { 'key1' : 2 }, $set: { 'key1' : 5 }})
Notice that here I'm trying to update the value of key1 from two MongoDB Update Operators.
This basically happens when you try to update the value of a same key with more than one MongoDB Update Operators within the same query.
You can find a list of Update Operators over here

If you pass the same key in $set and in $unset when updating an item, you will get that error.
For example:
const body = {
_id: '47b82d36f33ad21b90'
name: 'John',
lastName: 'Smith'
}
MyModel.findByIdAndUpdate(body._id, { $set: body, $unset: {name: 1}})
// Updating the path 'name' would create a conflict at 'name'

You cannot have the same path referenced more than once in an update. For example, even though the below would result in something logical, MongoDB will not allow it.
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'address': {state: 'CA'}, 'address.city' : 'San Diego'}}
)
You would get the following error:
Updating the path 'address.city' would create a conflict at 'address'

db.products.update(
{ _id: 1 },
{
$set: { item: "apple" },
$setOnInsert: { defaultQty: 100 }
},
{ upsert: true }
)
Below is the key explanation to the issue:
MongoDB creates a new document with _id equal to 1 from the
condition, and then applies the $set AND $setOnInsert operations to
this document.
If you want a field value is set or updated regardless of insertion or update, use it in $set. If you want it to be set only on insertion, use it in $setOnInsert.
Here is the example: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#example

Starting from MongoDB 4.2 you can use aggregate pipelines in update:
db.your_collection.update({
_id: 1
},
[{
$set:{
x_field: {
$cond: {
if: {$eq:[{$type:"$_id"} , "missing"]},
then: 'upsert value', // it's the upsert case
else: '$x_field' // it's the update case
}
}
}
}],
{
upsert: true
})
db.collection.bulkWrite() also supports it

With the Ruby library at least, it's possible to get this error if you have the same key twice, once as a symbol and once as a string:
db.getCollection("user").updateOne(
{_id: ...},
{$set: {'name': "Horse", name: "Horse"}}
)

I recently had the same issue while using the query below.
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.note': req.body.note } }, { upsert: true, new: true })
When i have changed 'content.note' to 'content.$.note' it has been fixed. So my final query is :
TextContainer.findOneAndUpdate({ blockId: req.params.blockId, 'content._id': req.params.noteId }, { $set: { 'content.$.note': req.body.note } }, { upsert: true, new: true })

Related

mongodb - how to insert a new key/value on each array's element if not present (with mongo query)

I would like to update each elements (object) in an array of a company.
Here my actual data :
{
_id: ObjectId("60d31024860ce0400b586111")
contracts:
[
{
name: 1.pdf
url: "https://someurl"
createdAt: 2021-06-23T10:42:44.594+00:00
}
{
name: 2.pdf
url: "https://someurl"
}
{
name: 3.pdf
url: "https://someurl"
}
]
}
I would like to add a defined date on each object (in contracts) that has no "updatedAt" key.
Here what I tried :
db.companies.update({ _id: ObjectId("60d31024860ce0400b586111"),"contracts.createdAt": { $exists: false } },{ $set: { "contracts.$.createdAt": "test" } })
but I got this error :
"The positional operator did not find the match needed from the query."
I have also tried this and it works, but I don't wanna query by file name. I just wanna add "createdAt" on each elements found that has no "createdAt"
db.companies.update({ "contracts.name": "2.pdf" },{ $set: { "contracts.$.createdAt": "atest" } })
I think you need to use the filtered position operator:
$ - updates the first matched array element
$[] - updates all the matched elements with a specific condition
The specific condition is mentioned in the arrayFilters key.
db.students.update(
{ },
{ $set: { "contracts.$[element].createdAt" : "atest"} },
{ multi: true,
arrayFilters: [ { "element.createdAt": { $exists: false } } ]
}
)
multi - true is to apply the operation on all the matching documents.
Also notice, how the first query parameter is empty, which means the query runs for all documents. I used it based on the second query you wrote but you can also add in an ObjectID query there.

Mongodb: concat to existing document

I have my collection like this:
{
"_id" : "ID1234",
"read_object" : "sss-ssss",
"expireAt" : ISODate("2020-04-30T22:00:00.000Z")
}
In case he encounters the same ID, I would like to update the read_object field, otherwise create a new document.
I tried to do it like this:
db.collection.update(
{ _id: "ID1234" },
{
$set: { read_object: { $concat: ["$read_object", "test"] } },
},
{ upsert: true }
)
but I get an error every time:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
If I add square brackets before $set, like this:
db.collection.update(
{ _id: "1b1b871493-14a0-4d21-bd74-086442df953c-2020-02" },
[{
$set: { read_object: { $concat: ["$read_object", "test"] } },
}],
{ upsert: true }
)
I get this error:
The dollar ($) prefixed field '$concat' in 'read_object.$concat' is not valid for storage.
Where do I have a mistake?
$concat is an aggregation operator, meaning you can't use it while using the basic update syntax as you can only use update operators on it.
With that said Mongo version 4.2 introduces pipeline updates, which is basically what you're trying to do with the square brackets.
Assuming you are using Mongo version 4.2 heres a working example:
db.test1.update({_id: "ID1234"}, [
{$set: {"read_object": {$concat: [{$ifNull: ["$read_object", ""]}, "test"]}}}
], {upsert: true});
Basically we just need to "replace" read_object if document does not exist as it is undefined in that case.
If you are using Mongo version that's smaller than 4.2 then unfortunately there is no way to do what you want in one operation, you'll have to first read the document and then adjust accordingly.

FindOne followed by updateOne, using a single query

By using the "findOne" method of mongoDb driver (no mongoose), can I use the retrieved document to update it later?
For example:
document.collection('myCollection').findOne({ _id: myId }, (err, foundDoc) => {
// **I need to do some multiple checks in between**, so I don't want to use "findOneAndUpdate()"
// foundDoc is the retrieved document. Can I use it directly for update? (something like "foundDoc.update()")
document.collection('myCollection').updateOne({ _id: myId }, { $set: { name: 'John' } });
});
As you can see I am basically doing a second query by using the "updateOne" method (first it searches for the document and then it updates it). Can I avoid that somehow, and use the foundDoc directly for update?
If you want to update the same document, you don't have to call .findOne() followed by .updateOne() method. By default, upsert option is set to false in .upadateOne(), so it will refuse to insert the document if not found else it will update.
document.collection('myCollection').updateOne({ _id: myId }, { $set: { name: 'John' } });
.updateOne should be sufficient in your case.
Also if you want to add some filter conditions, .updateOne() supports that as below :
db.collection.updateOne(
<filter>, // you can place filters here
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ]
}
)
Link : https://docs.mongodb.com/manual/reference/method/db.collection.updateOne/

How to add ObjectId property to each object of an array

I am have manually created an object in a Mongo collection:
{
"messages": [
{
"url":"http://test.test.com",
"message":"test message"
}
],
....other properties
}
I would like to add an _id:ObjectId() to each item of my messages array and for each document in the collection.
I tried:
collection.update({}, {
$set: {
'messages.$._id': ObjectId(),
},
}, { multi: true }
but this is not working. The Id is getting added when I add new ones going through Mongoose, but these were manually entered into mongo. Any help is appreciated.
Your syntax is correct, but in order to use the $ positional operator the array field must appear as part of the query document, check in documentation.
Try this:
db.collection.update({messages: {$exists: true }},
{$set: { 'messages.$._id': ObjectId() } },
{multi: true}
)

MongoDB: Too many positional (i.e. '$') elements found in path

I just upgraded to Mongo 2.6.1 and one update statement that was working before is not returning an error. The update statement is:
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$.comments.$.name': 'joe'
}},
{ multi: true }
)
The error I get is:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 2,
"errmsg" : "Too many positional (i.e. '$') elements found in path 'answers.$.comments.$.createUsername'"
}
})
When I update an element just one level deep instead of two (i.e. answers.$.name instead of answers.$.comments.$.name), it works fine. If I downgrade my mongo instance below 2.6, it also works fine.
You CAN do this, you just need Mongo 3.6! Instead of redesigning your database, you could use the Array Filters feature in Mongo 3.6, which can be found here:
https://thecodebarbarian.com/a-nodejs-perspective-on-mongodb-36-array-filters
The beauty of this is that you can bind all matches in an array to a variable, and then reference that variable later. Here is the prime example from the link above:
Use arrayFilters.
MongoDB 3.5.12 extends all update modifiers to apply to all array
elements or all array elements that match a predicate, specified in a
new update option arrayFilters. This syntax also supports nested array
elements.
Let us assume a scenario-
"access": {
"projects": [{
"projectId": ObjectId(...),
"milestones": [{
"milestoneId": ObjectId(...),
"pulses": [{
"pulseId": ObjectId(...)
}]
}]
}]
}
Now if you want to add a pulse to a milestone which exists inside a project
db.users.updateOne({
"_id": ObjectId(userId)
}, {
"$push": {
"access.projects.$[i].milestones.$[j].pulses": ObjectId(pulseId)
}
}, {
arrayFilters: [{
"i.projectId": ObjectId(projectId)
}, {
"j.milestoneId": ObjectId(milestoneId)
}]
})
For PyMongo, use arrayFilters like this-
db.users.update_one({
"_id": ObjectId(userId)
}, {
"$push": {
"access.projects.$[i].milestones.$[j].pulses": ObjectId(pulseId)
}
}, array_filters = [{
"i.projectId": ObjectId(projectId)
}, {
"j.milestoneId": ObjectId(milestoneId)
}])
Also,
Each array filter must be a predicate over a document with a single
field name. Each array filter must be used in the update expression,
and each array filter identifier $[] must have a corresponding
array filter. must begin with a lowercase letter and not contain
any special characters. There must not be two array filters with the
same field name.
https://jira.mongodb.org/browse/SERVER-831
The positional operator can be used only once in a query. This is a limitation, there is an open ticket for improvement: https://jira.mongodb.org/browse/SERVER-831
As mentioned; more than one positional elements not supported for now. You may update with mongodb cursor.forEach() method.
db.post
.find({"answers.comments.name": "jeff"})
.forEach(function(post) {
if (post.answers) {
post.answers.forEach(function(answer) {
if (answer.comments) {
answer.comments.forEach(function(comment) {
if (comment.name === "jeff") {
comment.name = "joe";
}
});
}
});
db.post.save(post);
}
});
db.post.update(
{ 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$[i].comments.$.name': 'joe'
}},
{arrayFilters: [ { "i.comments.name": { $eq: 'jeff' } } ]}
)
check path after answers for get key path right
I have faced the same issue for the as array inside Array update require much performance impact. So, mongo db doest not support it. Redesign your database as shown in the given link below.
https://pythonolyk.wordpress.com/2016/01/17/mongodb-update-nested-array-using-positional-operator/
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.$.comments.$.name': 'joe'
}},
{ multi: true }
)
Answer is
db.post.update( { 'answers.comments.name': 'jeff' },
{ '$set': {
'answers.0.comments.1.name': 'joe'
}},
{ multi: true }
)