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.
Update Nested Arrays in Conjunction with $[]The $[<identifier>] filtered positional operator, in conjunction with the $[] all positional operator, can be used to update nested arrays. The following updates the values that are greater than or equal to 8 in the nested grades. questions array if the associated grades.
Update Documents in an ArrayThe positional $ operator facilitates updates to arrays that contain embedded documents. Use the positional $ operator to access the fields in the embedded documents with the dot notation on the $ operator.
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:
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
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
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' } } ]}
)
answers
for get key path right
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With