51

I have a document in mongodb with 2 level deep nested array of objects that I need to update, something like this:

{
    id: 1,
    items: [
        {
            id: 2,
            blocks: [
                {
                    id: 3
                    txt: 'hello'
                }
            ]
        }
    ] 
}

If there was only one level deep array I could use positional operator to update objects in it but for second level the only option I've came up is to use positional operator with nested object's index, like this:

db.objects.update({'items.id': 2}, {'$set': {'items.$.blocks.0.txt': 'hi'}})

This approach works but it seems dangerous to me since I'm building a web service and index number should come from client which can send say 100000 as index and this will force mongodb to create an array with 100000 indexes with null value.

Are there any other ways to update such nested objects where I can refer to object's ID instead of its position or maybe ways to check if supplied index is out of bounds before using it in query?

danronmoon
  • 3,814
  • 5
  • 34
  • 56
src091
  • 2,807
  • 7
  • 44
  • 74
  • 4
    I would suggest you revisit this schema and find different design so that you can leverage the power that MongoDB provides. There is going to be no easy/super efficient way to update a specific item in the array AFAIK. Can you redesign this so that you can take adavantage of `addToSet`, `pop` and the other array operators? – brianz Nov 08 '10 at 18:29
  • Thanks for suggestion, yes I can and in fact I already did that. Asking this question I just wanted to be sure I'm not missing anything. – src091 Nov 08 '10 at 22:50
  • 2
    I am also facing the same problem.So can you post your sample redesigned schema ? – Damodaran Nov 12 '12 at 10:27

6 Answers6

30

Here's the big question, do you need to leverage Mongo's "addToSet" and "push" operations? If you really plan to modify just individual items in the array, then you should probably build these arrays as objects.

Here's how I would structure this:

{
    id: 1,
    items: 
        { 
          "2" : { "blocks" : { "3" : { txt : 'hello' } } },
          "5" : { "blocks" : { "1" : { txt : 'foo'}, "2" : { txt : 'bar'} } }
        }
}

This basically transforms everything in to JSON objects instead of arrays. You lose the ability to use $push and $addToSet but I think this makes everything easier. For example, your query would look like this:

db.objects.update({'items.2': {$exists:true} }, {'$set': {'items.2.blocks.0.txt': 'hi'}})

You'll also notice that I've dumped the "IDs". When you're nesting things like this you can generally replace "ID" with simply using that number as an index. The "ID" concept is now implied.

This feature has been added in 3.6 with expressive updates.

db.objects.update( {id: 1 }, { $set: { 'items.$[itm].blocks.$[blk].txt': "hi", } }, { multi: false, arrayFilters: [ { 'itm.id': 2 }, { 'blk.id': 3} ] } )

s7vr
  • 73,656
  • 11
  • 106
  • 127
Gates VP
  • 44,957
  • 11
  • 105
  • 108
  • if `items` is an array, that `$set` command does not work. You'll need to structure the data differently and leverage `$addToSet`. – Gates VP Jun 28 '13 at 05:42
  • 1
    I was looking for a solution using the `$`. Something like `items.$` that would iterate over all the elements of the item array and make the change that I want. Any ideas what the syntax would be for something like this. Can it even be done? – kapad Jun 28 '13 at 21:15
  • I agree with @kapad, im more interested in using multiple positional operators: 'items.$.blocks.$.txt'... Unfortunetly functionality like this is still not available See this ticket: [SERVER-831](https://jira.mongodb.org/browse/SERVER-831) Login to mongodb's JIRA and vote for this issue! – Daniel Macias Nov 07 '13 at 18:56
  • I come here for `updating nested arrays in mongodb`, but this answer help little to the question. vote down. – Jiang YD Jul 13 '16 at 11:43
  • That's a great idea actually. However, how would you make queries of this nature: "find all items that have a nested txt value that is equal to hello" – ppoliani Dec 17 '16 at 22:56
5

Building on Gates' answer, I came up with this solution which works with nested object arrays:

db.objects.updateOne({
  ["items.id"]: 2
}, {
  $set: {
    "items.$.blocks.$[block].txt": "hi",
  },
}, {
  arrayFilters: [{
    "block.id": 3,
  }],
});
Pier-Luc Gendreau
  • 13,553
  • 4
  • 58
  • 69
3

The ids which you are using are linear number and it has to come from somewhere like an additional field such 'max_idx' or something similar. This means one lookup for the id and then update. UUID/ObjectId can be used for ids which will ensure that you can use Distributed CRUD as well.

Seraj
  • 31
  • 1
1

MongoDB 3.6 added all positional operator $[] so if you know the id of block that need update, you can do something like:

db.objects.update({'items.blocks.id': id_here}, {'$set': {'items.$[].blocks.$.txt': 'hi'}})
Cuong Le Ngoc
  • 11,595
  • 2
  • 17
  • 39
1

db.col.update({"items.blocks.id": 3},
{ $set: {"items.$[].blocks.$[b].txt": "bonjour"}},
{ arrayFilters: [{"b.id": 3}] }
)

https://docs.mongodb.com/manual/reference/operator/update/positional-filtered/#update-nested-arrays-in-conjunction-with

Katya Kamenieva
  • 316
  • 1
  • 5
0

This is pymongo function for find_one_and_update. I searched a lot to find the pymongo function. Hope this will be useful

find_one_and_update(filter, update, projection=None, sort=None, return_document=ReturnDocument.BEFORE, array_filters=None, hint=None, session=None, **kwargs)

Example

db.pymongo_object.find_one_and_update( filter = {'id' : 1}, update= {$set: {"items.$[array1].blocks.$[array2].txt": "hi"}}, array_filters =[{"array1.id" :2}, {"array2.id": 3}]) 

Also see pymongo documentation.

buddemat
  • 4,552
  • 14
  • 29
  • 49
Prat
  • 1
  • 1
  • db.pymongo_object.find_one_and_update( filter = {'id' : 1}, update= {$set: {"items.$[array1].blocks.$[array2].txt": "hi"}}, array_filters =[{"array1.id" :2}, {"array2.id": 3}]) – Prat Feb 22 '23 at 07:10
  • https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html – Prat Feb 22 '23 at 07:10