17

Is there an easy way to replace an entire embedded document in an array? Say replacing:

{
   "_id" : "2",
      "name" : "name2",
      "xyz..." : "xyz2..."
}

with:

{
   "_id" : "2",
      "name" : "name6",
      "xyz..." : "xyz5..."
      "morefields..." : "fields..."
}

Searching for _id (embedded). Or do I need to replace each field individually using $set?

{
  "_id" : "2",
  "users" : [{
      "_id" : "1",
      "name" : "name1",
      "xyz..." : "xyz1..."
    }, {
      "_id" : "2",
      "name" : "name2",
      "xyz..." : "xyz2..."
    }],
  "name" : "main name"
}
Fredrik L
  • 1,790
  • 4
  • 21
  • 28

1 Answers1

26

You are using the "array of objects" pattern. You can use the positional operator, it should look something like this:

coll.update( {'_id':'2', 'users._id':'2'}, {$set:{'users.$':{ "_id":2,"name":"name6",... }}}, false, true)

In my experience, the "array of objects" pattern is not optimal if the objects have a natural ID. In your case, this could be modeled as the following:

{
  "_id" : "2",
  "users" : 
    { "1" : { "name" : "name1", "xyz..." : "xyz1..." }, 
      "2" : { "name" : "name2", "xyz..." : "xyz2..." }
    }
  "name" : "main name"
}

In this case you can use the dot notation to easily update the item you want.

var newValue = {  "name" : "name6", "xyz..." : "xyz5...", "morefields..." : "fields..." };
coll.update({_id: 2}, { $set: { "users.2" : newValue } });
Gates VP
  • 44,957
  • 11
  • 105
  • 108
  • That works great, thanks! Interesting the 2nd alternative but I'm not sure it will work since I'll need to remove some entries in the middle. "users.2" is the position in the array I assume. – Fredrik L Feb 08 '12 at 21:24
  • I thought users.2 referred to the array position but it's actually using the id (and returning empty brackets for all other array items}, cool. – Fredrik L Feb 08 '12 at 23:29
  • MongoDB recognizes arrays in a query. So `users.2` could search through the `users` array looking for objects with `key` of `2` *or* it could look at the `key:2` of `users`. – Gates VP Feb 08 '12 at 23:36
  • 2
    I was going with your second model but now I'm thinking I won't be able to index the inner name field. If it was an array it would be users.name but here it's users.1.name users.2.name? – Fredrik L Feb 11 '12 at 05:31
  • 3
    You are correct, the second structure does not index very well. This is definitely a trade-off. But you cannot optimize for all queries with MongoDB. If you want this query to be fast, then you have to trade off ease of updating. – Gates VP Feb 12 '12 at 20:44
  • This is a brilliant pattern! And I suppose to aid indexing, one could store users ids in an array (users_ids for example). – Dmitry Jan 19 '13 at 06:26
  • I suppose there is no way to use your second pattern when the natural ID is compound? (two integers, i.e. x,y coords). – UpTheCreek Feb 27 '13 at 11:22
  • But is it possible to (de)serialize this into strong types? – Jiří Herník Mar 20 '17 at 07:59
  • Thanks for this. I have a bit of a different challenge (trying to get Mongo to act close enough to Firestore to use a lot of the same code) and this was the key idea that let me pull it off. – samanime Oct 28 '20 at 16:25