10

I have document like

{
    id : 100,
    heros:[
        {
           nickname : "test",
           spells : [
             {spell_id : 61, level : 1},
             {spell_id : 1, level : 2}
           ]
        }
    ]
}

I can't $set spell's level : 3 with spell_id : 1 inside spells that inside heros with nickname "test. I tried this query:

db.test.update({"heros.nickname":"test", "heros.spells.spell_id":1}, 
{$set:{"heros.spells.$.level":3}});

Errror i see is

can't append to array using string field name [spells] Thanks for help.

dcrosta
  • 26,009
  • 8
  • 71
  • 83
Denis Ermolin
  • 5,530
  • 6
  • 27
  • 44

2 Answers2

12

You can only use the $ positional operator for single-level arrays. In your case, you have a nested array (heros is an array, and within that each hero has a spells array).

If you know the indexes of the arrays, you can use explicit indexes when doing an update, like:

> db.test.update({"heros.nickname":"test", "heros.spells.spell_id":1}, {$set:{"heros.0.spells.1.level":3}});
dcrosta
  • 26,009
  • 8
  • 71
  • 83
3

Try something like this:

db.test.find({"heros.nickname":"test"}).forEach(function(x) {  
    bool match = false;
    for (i=0 ; i< x.heros[0].spells.length ; i++) {
        if (x.heros[0].spells[i].spell_id == 1) 
        {
            x.heros[0].spells[i].level = 3;
            match = true;
        }
    }
    if (match === true) db.test.update( { id: x.id }, x );
});

Apparently someone opened a ticket to add the ability to put a function inside the update clause, but it hasn't been addressed yet: https://jira.mongodb.org/browse/SERVER-458

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • Your code doesn't work. I think in x will be all document isn't it? – Denis Ermolin May 06 '12 at 09:31
  • @DenisErmolin oops ... yeah, that was wrong. I took another shot at it (see update). **x** is the document, right, so I'm thinking you should be able to update it by the **id** property. – McGarnagle May 06 '12 at 09:40
  • x.spells reference to spells but spells in heros array – Denis Ermolin May 06 '12 at 09:41
  • It does work well for my, only need to change the variable from: bool match = false to $match = false, then use $match to reference. – JhonQO Oct 31 '17 at 17:07
  • @McGarnagle but this code will not be atomic as between find and update, suppose you find spell_id == 1, at index 1 and at the same time some other query added/removed embedded document at index 1, now what will happen is when your query will update in mongo, then it will put updated value at index 1, which will overwrite the document which got added/removed previously at index 1. – Sudhanshu Gaur Nov 29 '18 at 18:14