Обновление вложенных массивов в 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 .