MongoDB - Memperbarui objek dalam array dokumen (pembaruan bersarang)

204

Asumsikan kita memiliki koleksi berikut, yang saya punya beberapa pertanyaan tentang:

{
    "_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 - Saya ingin menaikkan harga untuk "item_name": "my_item_two" dan jika tidak ada , itu harus ditambahkan ke array "items".

2 - Bagaimana saya bisa memperbarui dua bidang sekaligus. Misalnya, naikkan harga untuk "my_item_three" dan pada saat yang sama naikkan "total" (dengan nilai yang sama).

Saya lebih suka melakukan ini di sisi MongoDB, kalau tidak saya harus memuat dokumen di sisi klien (Python) dan membangun dokumen yang diperbarui dan menggantinya dengan yang ada di MongoDB.

UPDATE Ini adalah apa yang saya coba dan berfungsi dengan baik JIKA ADA Objek Ada :

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

Tetapi jika kuncinya tidak ada itu tidak melakukan apa-apa. Juga hanya memperbarui objek bersarang. Tidak ada cara dengan perintah ini untuk memperbarui bidang "total" juga.

Majid
sumber
1
Saya pikir Anda tidak bisa melakukan ini di mongo, kecuali mungkin dengan banyak rasa sakit menggunakan eval. Mongo sangat terbatas dalam operasi data.
Antti Haapala
3
@ Haapala: mongodb memiliki $ inc dan memperbarui dengan upert
jdi
1
@ Jdi ya ya, tapi itu tidak banyak membantu di sini, tetapi yang dia butuhkan adalah beberapa $ incs, kondisional, dan jika item tersebut tidak ada, maka $ push diperlukan.
Antti Haapala

Jawaban:

237

Untuk pertanyaan # 1, mari kita bagi menjadi dua bagian. Pertama, tambahkan dokumen apa pun yang memiliki "items.item_name" sama dengan "my_item_two". Untuk ini, Anda harus menggunakan operator "$" posisional. Sesuatu seperti:

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

Perhatikan bahwa ini hanya akan menambah subdokumen yang cocok pertama dalam array apa pun (jadi jika Anda memiliki dokumen lain dalam array dengan "item_name" sama dengan "my_item_two", itu tidak akan bertambah). Tapi ini mungkin yang Anda inginkan.

Bagian kedua lebih rumit. Kami dapat mendorong item baru ke array tanpa "my_item_two" sebagai berikut:

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

Untuk pertanyaan Anda # 2, jawabannya lebih mudah. Untuk menambah total dan harga item_three dalam dokumen apa pun yang berisi "my_item_three," Anda dapat menggunakan operator $ inc pada banyak bidang sekaligus. Sesuatu seperti:

db.bar.update( {"items.item_name" : {$ne : "my_item_three" }} ,
               {$inc : {total : 1 , "items.$.price" : 1}} ,
               false ,
               true);
matulef
sumber
Terima kasih balasannya. Jawaban untuk Pertanyaan # 2 sempurna dan berfungsi dengan baik :) Tapi untuk Pertanyaan # 1, masalahnya adalah Anda harus mencari dokumen dengan "user_id", dan bukan dengan "items.item_name". Dalam hal ini saya tidak dapat menggunakan Operator Posisi, karena saya belum menentukan array dalam permintaan pencarian. Saya juga ingin kedua bagian dilakukan dalam satu tembakan. (jika mungkin)
Majid
Contoh yang bagus. Saya merasa seolah-olah saya membuat arah ini jelas dari tautan dalam jawaban saya, tetapi saya terus mendapatkan perlawanan dengan mengatakan itu bukan apa yang dia cari. Tebak OP hanya perlu melihat contoh bagaimana melakukan multiple $ inc.
jdi
Untuk pertanyaan # 1, Anda masih dapat melakukannya dalam dua bagian dengan menambahkan user_id ke bit kueri dari setiap pembaruan (saya akan memodifikasi jawabannya sesuai). Harus memikirkan lebih lanjut tentang apakah mungkin dilakukan dalam satu tembakan.
matulef
6
Apa parameter 3 dan 4 dalam metode pembaruan? dan mengapa?
skmahawar
5
@skmahawar mengenai params ke-3 dan ke-4, docs.mongodb.com/manual/reference/method/db.collection.update menunjukkan opsi-opsi ini untuk masing-masing "upsert" dan "multi". Untuk upsert, jika disetel ke true, buat dokumen baru saat tidak ada dokumen yang cocok dengan kriteria kueri. Nilai defaultnya salah, yang tidak menyisipkan dokumen baru ketika tidak ada kecocokan yang ditemukan. "Untuk multi, Jika disetel ke true, perbarui beberapa dokumen yang memenuhi kriteria kueri. Jika disetel ke false, perbarui satu dokumen. Nilai defaultnya adalah false.
Mike Strand
24

Tidak ada cara untuk melakukan ini dalam permintaan tunggal. Anda harus mencari dokumen di kueri pertama:

Jika ada dokumen:

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

Lain

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

Tidak perlu menambahkan kondisi {$ne : "my_item_two" } .

Juga dalam lingkungan multithreaded Anda harus berhati-hati bahwa hanya satu utas yang dapat mengeksekusi yang kedua (masukkan case, jika dokumen tidak ditemukan) pada suatu waktu, jika tidak, dokumen duplikat embed akan dimasukkan.

Udit
sumber
1
tidak, ada cara untuk melakukan ini dalam satu permintaan, lihat di atas
Martijn Scheffer
1
@ MartijnScheffer, saya tidak melihat cara untuk melakukan ini sebagai permintaan tunggal di mana saja di utas ini. Bahkan "jawaban" menggunakan 2 kueri yang sama seperti dalam jawaban ini. Apakah saya melewatkan sesuatu? Saya juga berharap ada cara untuk melakukan ini dalam satu permintaan untuk mencegah masalah multi-threading
dferraro
@Udit, bagaimana saya bisa menggunakan barang. $. Harga di dalam kondisi? ketika saya mencoba menambahkan kondisi seperti "kenaikan hanya jika harga lebih besar dari 0", itu tidak berfungsi - pada dasarnya tidak ada yang diperbarui. Saya dapat menggunakan ini dalam kondisi di mana, atau saya kehilangan sesuatu? item. $. price: {$ gt: 0}
dferraro
pasti ada sesuatu yang hilang :)
Martijn Scheffer
3

Kita dapat menggunakan $setoperator untuk memperbarui array bersarang di dalam objek yang diajukan, memperbarui nilai

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
KARTHIKEYAN.A
sumber