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 так, але це не дуже допомагає тут, але те, що йому потрібно - це декілька $ дюймів, умовно, а якщо елемента не існує, знадобиться $ push.
Антті Хаапала

Відповіді:


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);

Дякую за відповідь. Відповідь на запитання №2 ідеальна і працює чудово :) Але для питання №1 проблема полягає в тому, що ви повинні шукати документ за допомогою "user_id", а не за "items.item_name". У цьому випадку я не можу використовувати Позиційний Оператор, оскільки я не вказав масив у пошуковому запиті. Також я хочу, щоб обидві частини були виконані в один кадр. (якщо можливо)
Маджід

Приємні приклади. Мені здавалося, що я роблю цей напрямок очевидним із посилань у своїй відповіді, але я постійно отримував опір, кажучи, що це не те, що він шукав. Здогадайтесь, OP потрібен лише для того, щоб побачити приклади того, як робити кілька $ inc.
jdi

У питанні №1 ви все одно можете це зробити у двох частинах, додавши user_id до біта запиту кожного з оновлень (відповідь я зміню відповідно). Доведеться подумати більше про те, чи можна це зробити за один кадр.
matulef

6
Які 3-й та 4-й параметри у способі оновлення? і чому?
skmahawar

5
@skmahawar щодо 3-го і 4-го парам , docs.mongodb.com/manual/reference/method/db.collection.update вказує, що ці параметри відповідно "upsert" і "multi". Для upsert, якщо встановлено значення true, створює новий документ, коли жоден документ не відповідає критеріям запиту. Значення за замовчуванням - false, яке не вставляє новий документ, коли не знайдено відповідності. "Для multi, якщо встановлено значення true, оновлення декількох документів, що відповідають критеріям запиту. Якщо встановлено значення false, оновить один документ. Значенням за замовчуванням є 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" }.

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


1
ні, є спосіб зробити це за допомогою одного запиту, див. вище
Martijn Scheffer

1
@MartijnScheffer, я не бачу способу зробити це як єдиний запит будь-де в цій темі. Навіть "відповідь" використовує 2 запити так само, як у цій відповіді. Я щось пропускаю? Я також сподіваюся, що є спосіб зробити це за один запит, щоб запобігти виникненню будь-яких проблем із багатопотоковою
ниткою

@Udit, як я можу використовувати предмети. $. Ціна всередині умови? коли я намагаюся додати умову, наприклад "приріст, лише якщо ціна перевищує 0", це не працює - в основному нічого не оновлюється. Чи можу я використовувати це в умовах, коли я щось пропускаю? предметів. $ ціна: {$ gt: 0}
dferraro

щось, мабуть, зникло :)
Martijn Scheffer

3

Ми можемо використовувати $setоператор для оновлення вкладеного масиву всередині поданого об'єкта оновлення значення

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.