3

I'm having a bit of a mongo issue. I was wondering if there was a way to do the following in a mongo console command rather then multiple find and update calls.

{
    "_id" : ObjectId("50b429ba0e27b508d854483e"),
    "array" : [
        {
            "id" : "1",
            "letter" : "a"
        },
        {
            "id" : "2",
            "letter" : "b"
        }
    ],
    "tester" : "tom"
}

I want to update the object with this new array item

{
    "id": "2",
    "letter": "c"
}

I used this, addToSet is limited, it won't insert an item into the array if it is already there but it will not update an item based on an identifier. In this case I would really like to update this entry based on the id.

db.soup.update({
     "tester": "tom"
 }, {
     $addToSet: {
         "array": {
             "id": "2",
             "letter": "c"
         }
     }
});

This gives me:

{
    "_id" : ObjectId("50b429ba0e27b508d854483e"),
    "array" : [
        {
            "id" : "1",
            "letter" : "a"
        },
        {
            "id" : "2",
            "letter" : "b"
        },
        {
            "id" : "2",
            "letter" : "c"
        }
    ],
    "tester" : "tom"
}

When what I really wanted was:

{
    "_id" : ObjectId("50b429ba0e27b508d854483e"),
    "array" : [
        {
            "id" : "1",
            "letter" : "a"
        },
        {
            "id" : "2",
            "letter" : "c"
        }
    ],
    "tester" : "tom"
}
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424

2 Answers2

7

You can use the $ positional operator to do this:

db.soup.update(
    {_id: ObjectId("50b429ba0e27b508d854483e"), 'array.id': '2'}, 
    {$set: {'array.$.letter': 'c'}})

The $ in the update object acts as a placeholder for the first element of array to match the query selector.

JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • This is great. I just realized that this is what I have. The more complicated question is now, how do I also get it to push a new object eg `{"id":3,"letter":"d"}` in a sort of upsert kind of way into the array? Do I have to programmatically sort the requests out? If it exists use `$set` if not `$push`? – ThomasReggi Nov 27 '12 at 15:44
  • @ThomasReggi It would be better if you asked that as a separate question. – JohnnyHK Nov 27 '12 at 15:47
  • http://stackoverflow.com/questions/13588342/can-mongo-handle-this-or-do-i-have-to-do-this-programmatically – ThomasReggi Nov 27 '12 at 16:11
0

Here you go:

> db.collection.insert( { array : [ { id : 1, letter : 'a' }, { id : 2, letter : 'b' } ], tester : 'tom' } );
> db.collection.findOne();
{
    "_id" : ObjectId("50b431a69a0358d590a2f5f0"),
    "array" : [
        {
            "id" : 1,
            "letter" : "a"
        },
        {
            "id" : 2,
            "letter" : "b"
        }
    ],
    "tester" : "tom"
}
> db.collection.update( { tester : 'tom' }, { $set : { 'array.1' : { id : 2, letter : 'c' } } }, false, true );
> db.collection.findOne();
{
    "_id" : ObjectId("50b431a69a0358d590a2f5f0"),
    "array" : [
        {
            "id" : 1,
            "letter" : "a"
        },
        {
            "id" : 2,
            "letter" : "c"
        }
    ],
    "tester" : "tom"
}

The trick lies in the false, true, false. That is: true for upsert, false for update multiple.

For more details check out: http://www.mongodb.org/display/DOCS/Updating#Updating-update%28%29

ddoxey
  • 2,013
  • 1
  • 18
  • 25
  • You are updating based on the array position (`array.1`) rather than finding the entry where `id` is `2`. This only works if you already know the position of the element in the array, rather than just the `id`. – Stennie Nov 27 '12 at 04:35
  • The question implied that the criterion was { tester : 'tom' } and that he wanted to replace the second element of the 'array' list. If the criterion must also include { 'array.id' : 2 } then JohnnyHK's solution looks like the winner here. – ddoxey Nov 27 '12 at 04:43