MongoDB - обновление объектов в массиве документов (вложенное обновление)

Предположим, что мы имеем следующий сборник, в котором у меня есть несколько вопросов о:

{
    "_id" : ObjectId("4faaba123412d654fe83hg876"),
    "user_id" : 123456,
    "total" : 100,
    "items" : [
            {
                    "item_name" : "my_item_one",
                    "price" : 20
            },
            {
                    "item_name" : "my_item_two",
                    "price" : 50
            },
            {
                    "item_name" : "my_item_three",
                    "price" : 30
            }
    ]
}

1 - Я хочу увеличить цену для "item_name": "my_item_two" , и если он не существует, он должен быть добавлен к массиву "items".

2 - Как я могу обновить два поля одновременно. Например, увеличьте цену для "my_item_three" и в то же время увеличьте "общее" (с тем же значением).

Я предпочитаю делать это на стороне MongoDB, иначе мне нужно загрузить документ на стороне клиента (Python) и создать обновленный документ и заменить его на существующий в MongoDB.

UPDATE Это то, что я пробовал и работает отлично ЕСЛИ ИМЕЕТСЯ ИСТОРИЯ:

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})

Но если ключ не существует, он ничего не делает. Также он обновляет только вложенный объект. У этой команды нет способа обновить поле "total".

Ответ 1

В вопросе №1 разложите его на две части. Во-первых, увеличьте любой документ, который имеет "items.item_name", равный "my_item_two" . Для этого вам придется использовать позиционный оператор "$". Что-то вроде:

 db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Обратите внимание, что это только увеличит первый согласованный поддокумент в любом массиве (так что если у вас есть другой документ в массиве с "item_name", равным "my_item_two" , он не будет увеличиваться). Но это может быть то, что вы хотите.

Вторая часть сложнее. Мы можем вывести новый элемент в массив без "my_item_two" следующим образом:

 db.bar.update( {user_id : 123456, "items.item_name" : {$ne : "my_item_two" }} , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

По вашему вопросу №2 ответ проще. Чтобы увеличить общую сумму и цену item_three в любом документе, который содержит "my_item_three", вы можете одновременно использовать оператор $inc на нескольких полях. Что-то вроде:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);

Ответ 2

В одном запросе нет способа сделать это. Вы должны искать документ в первом запросе:

Если документ существует:

db.bar.update( {user_id : 123456 , "items.item_name" : "my_item_two" } , 
                {$inc : {"items.$.price" : 1} } , 
                false , 
                true);

Else

db.bar.update( {user_id : 123456 } , 
                {$addToSet : {"items" : {'item_name' : "my_item_two" , 'price' : 1 }} } ,
                false , 
                true);

Не нужно добавлять условие {$ne : "my_item_two" }.

Также в многопоточной среде вы должны быть осторожны, чтобы только один поток мог выполнить второй (вставить регистр, если документ не был найден) за раз, в противном случае будут вставлены дублирующие вложенные документы.