2

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.

a14m
  • 7,808
  • 8
  • 50
  • 67
user3315068
  • 91
  • 2
  • 7

2 Answers2

1

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);
    }
  }
})
Gergo Erdosi
  • 40,904
  • 21
  • 118
  • 94
  • Thanks Gergo for your response on this. This was really helpful, to avoid more than one statement for this operation I changed my model little bit. Thanks again for your help on this – user3315068 Apr 11 '14 at 14:27
0

symbol $ only modifies the last position of the last array in the search, and you can use $addToSet or $push for add items...

 > db.resource.find()
    { "_id" : 1, 
      "resources" : [
                     { "resource_id" : 1,   
                       "resource_list" : [ "item1","item2" ] },     
                     { "resource_id" : 2, 
                       "resource_list" : [ "item4", "item3" ] } ] }

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

    > db.resource.find()
    { "_id" : 1, "resources" : [
                             { "resource_id" : 1, 
                               "resource_list" : [ "item1", "item2" ] },
                             { "resource_id" : 2, 
                               "resource_list" : [ "item4", "item3", "item5" ] } ] }

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

    > db.resource.find()
    { "_id" : 1, "resources" : [ 
                               { "resource_id" : 1,
                                 "resource_list" : [ "item1", "item2" ] },
                               { "resource_id" : 2,
                                 "resource_list" : [ "item3","item5" ] } ] }

But is better run two querys in same time?

> db.algo.update({"resources.resource_id": 2,
                  "resources.resource_list": "item4"}, 
                 {$addToSet:{"resources.$.resource_list": "item5"},
                   $pull: {"resources.$.resource_list": "item4"}})

Field name duplication not allowed with modifiers

Carlos Rodriguez
  • 833
  • 8
  • 12
  • Thanks Carlos for your response, I didn't try $addToSet but tried $push and $pull in the same queries but that didn't work. Are you saying that instead of $push if I use $addToSet then it should work? – user3315068 Apr 11 '14 at 14:28
  • No, MongoDB says **"Field name duplication not allowed with modifiers"**, in short word, MongoDB don't allow delete and add value in same consult in same field. – Carlos Rodriguez Apr 11 '14 at 15:03