Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

mongodb

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.

like image 694
Jeff Whelpley Avatar asked Jun 04 '14 19:06

Jeff Whelpley


People also ask

How do I update a nested array in Mongodb?

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.

How do you update an array object in Mongodb?

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.


5 Answers

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:

enter image description here

like image 54
Big Sam Avatar answered Oct 23 '22 20:10

Big Sam


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

like image 32
Gergo Erdosi Avatar answered Oct 23 '22 20:10

Gergo Erdosi


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

like image 17
kartoon Avatar answered Oct 23 '22 18:10

kartoon


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);
    }
  });
like image 5
Fırat KÜÇÜK Avatar answered Oct 23 '22 18:10

Fırat KÜÇÜK


    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
like image 2
Sukron Ma'mun Avatar answered Oct 23 '22 19:10

Sukron Ma'mun