Обновление вложенных массивов в mongodb

У меня есть документ в mongodb с двухуровневым вложенным массивом объектов, который мне нужно обновить, что-то вроде этого:

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

Если бы существовал только один массив глубинного уровня, я мог бы использовать позиционный оператор для обновления объектов в нем, но для второго уровня единственный вариант, который я нашел, - это использовать позиционный оператор с индексом вложенного объекта, например так:

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

Этот подход работает, но он кажется мне опасным, так как я создаю веб-сервис, и индексный номер должен поступать от клиента, который может отправить, скажем, 100000 в качестве индекса, и это заставит mongodb создать массив с 100000 индексами с нулевым значением.

Существуют ли другие способы обновления таких вложенных объектов, в которых я могу ссылаться на идентификатор объекта, а не на его позицию, или, возможно, способы проверить, находится ли указанный индекс вне границ, прежде чем использовать его в запросе?

6 ответов

Вот большой вопрос, нужно ли вам использовать монго операции "addToSet" и "push"? Если вы действительно планируете изменять только отдельные элементы в массиве, то вам, вероятно, следует построить эти массивы как объекты.

Вот как я бы структурировал это:

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

Это в основном преобразует все в объекты JSON вместо массивов. Вы теряете способность использовать $push а также $addToSet но я думаю, что это делает все проще. Например, ваш запрос будет выглядеть так:

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

Вы также заметите, что я сбросил "идентификаторы". Когда вы вкладываете подобные вещи, вы обычно можете заменить "ID" простым использованием этого числа в качестве индекса. Понятие "ID" теперь подразумевается.

Эта функция была добавлена ​​в 3.6 с выразительными обновлениями.

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

Основываясь на ответе Гейтса, я придумал это решение, которое работает с вложенными массивами объектов:

db.objects.updateOne({
  ["items.id"]: 2
}, {
  $set: {
    "items.$.blocks.$[block].txt": "hi",
  },
}, {
  arrayFilters: [{
    "block.id": 3,
  }],
});

Идентификаторы, которые вы используете, являются линейными числами, и они должны быть откуда-то, например, в дополнительном поле, таком как 'max_idx' или что-то подобное. Это означает один поиск идентификатора и затем обновление. UUID/ObjectId можно использовать для идентификаторов, которые гарантируют, что вы также можете использовать Распределенный CRUD.

MongoDB 3.6 добавил все позиционные операторы $[], поэтому, если вы знаете идентификатор блока, который нужно обновить, вы можете сделать что-то вроде:

db.objects.update({'items.blocks.id': id_here}, {'$set': {'items.$[].blocks.$.txt': 'hi'}})

Это функция pymongo дляfind_one_and_update. Я много искал, чтобы найти функцию pymongo. Надеюсь, это будет полезно

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

Пример

      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}]) 

Также см. документацию pymongo .

Другие вопросы по тегам