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

204

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

{
    "_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.

ОБНОВЛЕНИЕ Это то, что я пытался и работает нормально, если объект существует :

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

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

Маджид
источник
1
Я думаю, что вы не можете сделать это в монго, кроме, может быть, с большой болью, используя eval. Монго очень ограничен в данных операций.
Антти Хаапала
3
@Haapala: у mongodb есть $ inc и обновление с upsert
jdi
1
@jdi да да, но здесь это не сильно помогает, но ему нужно несколько условных единиц $ incs, и, если элемент не существует, нужен толчок $.
Антти Хаапала

Ответы:

237

Для вопроса № 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);
matulef
источник
Спасибо за ответ. Ответ на вопрос № 2 идеален и работает отлично :) Но для вопроса № 1 проблема в том, что вы должны искать документ по «user_id», а не по «items.item_name». В этом случае я не могу использовать позиционный оператор, так как я не указал массив в поисковом запросе. Также я хочу, чтобы обе части были выполнены за один раз. (если возможно)
Маджид
Хорошие примеры. Я чувствовал, что я делал это направление очевидным из ссылок в моем ответе, но я продолжал получать сопротивление, говоря, что это не то, что он искал. Угадайте, что OP просто нужно, чтобы увидеть примеры того, как сделать несколько $ inc.
JDI
Для вопроса № 1 вы все равно сможете сделать это в двух частях, добавив user_id к биту запроса каждого из обновлений (я изменю ответ соответствующим образом). Придется больше думать о том, возможно ли это сделать за один выстрел.
matulef
6
Каковы 3-й и 4-й параметры в методе обновления? и почему?
скмахавар
5
@skmahawar относительно 3-го и 4-го параметров, docs.mongodb.com/manual/reference/method/db.collection.update указывает, что эти параметры предназначены для «upsert» и «multi» соответственно. Для upsert, если установлено значение true, создает новый документ, когда ни один документ не соответствует критериям запроса. Значением по умолчанию является false, которое не вставляет новый документ, когда совпадение не найдено. "Для multi, если установлено значение true, обновляет несколько документов, соответствующих критериям запроса. Если установлено значение false, обновляется один документ. Значение по умолчанию: ложь.
Майк Стрэнд
24

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

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

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

еще

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

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

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

Udit
источник
1
нет, есть способ сделать это одним запросом, см. выше
Мартин Шеффер
1
@MartijnScheffer, я не вижу способа сделать это одним запросом где-либо в этой теме. Даже «ответ» использует 2 запроса так же, как в этом ответе. Я что-то упускаю? Я также надеюсь, что есть способ сделать это в одном запросе, чтобы предотвратить любые проблемы многопоточности
dferraro
@Udit, как я могу использовать предметы. $. Price внутри условия? Когда я пытаюсь добавить условие, такое как «приращение, только если цена больше 0», оно не работает - в основном ничего не обновляется. Могу ли я использовать это в условиях где, или я что-то упустил? items. $. price: {$ gt: 0}
dferraro
что-то должно было исчезнуть :)
Мартин Шеффер
3

Мы можем использовать $setоператор для обновления вложенного массива в объекте поданного обновления значения

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
KARTHIKEYAN.A
источник