Search code examples
mongodbmongodb-update

How to update an element within an array of a sub-document in Mongodb


Facing issue while trying to update an element within an array of sub document in Mongo. If I consider the following document in the collection "resource"

{
    "_id": 1,
    "resources": [
        {
            "resource_id": 1,
            "resource_list": ["item1","item2"]
        },
        {
            "resource_id": 2,
            "resource_list": ["item4","item3"]
        }
    ]
}

I want to update "item4" with some other value like "item5" for "resource_id" = 2

The following statement gave me an error : Cannot apply the positional operator without a corresponding query field containing an array.

db.resource.update({"resources.resource_id": 2, "resources.resource_list": "item4"}, {$set: {"resources.$.resource_list.$": "item5"}})

Any help on this will be highly appreciated.


Solution

  • 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

    An other way to solve this problem could be to push "item5" to the array and pull "item4". Something like this:

    db.resource.update({ "resources.resource_id": 2, "resources.resource_list": "item4" }, { $pull: { "resources.$.resource_list": "item4" }, $push: { "resources.$.resource_list": "item5" } })
    

    However this isn't possible either, $pull and $push can't be used in the same query for the same field. Related ticket: https://jira.mongodb.org/browse/SERVER-1050

    I see two solutions. One is to execute two queries. In the first query, you push "item5", in the second query you pull "item4":

    db.resource.update({ "resources.resource_id": 2, "resources.resource_list": "item4" }, { $push: { "resources.$.resource_list": "item5" } });
    db.resource.update({ "resources.resource_id": 2, "resources.resource_list": "item4" }, { $pull: { "resources.$.resource_list": "item4" } });
    

    If you want to do it in one query, you can use mongo shell. Something like this:

    db.resource.find({ 'resources.resource_id': 2, 'resources.resource_list': 'item4' }).forEach( function(document) {
      for (var i in document.resources) {
        if (document.resources[i].resource_id == 2) {
          var index = document.resources[i].resource_list.indexOf('item4');
          document.resources[i].resource_list[index] = 'item5';
          db.resource.save(document);
        }
      }
    })