MongoDB - Bir belge dizisindeki nesneleri güncelleme (iç içe güncelleştirme)


205

Aşağıdaki koleksiyona sahip olduğumuzu varsayalım, bu konuda birkaç sorum var:

{
    "_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" için fiyatı artırmak istiyorum ve eğer mevcut değilse , "items" dizisine eklenmelidir.

2 - İki alanı aynı anda nasıl güncelleyebilirim. Örneğin, "my_item_three" fiyatını artırın ve aynı zamanda "toplam" ı (aynı değerle) artırın.

Bunu MongoDB tarafında yapmayı tercih ederim, aksi takdirde belgeyi istemci tarafına (Python) yüklemek ve güncellenmiş belgeyi oluşturmak ve MongoDB'deki mevcut belgeyle değiştirmek zorundayım.

GÜNCELLEME Nesne Varsa denedim ve iyi çalışıyor :

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

Ancak anahtar yoksa hiçbir şey yapmaz. Ayrıca yalnızca iç içe geçmiş nesneyi güncelleştirir. Bu komutla "toplam" alanını da güncellemenin bir yolu yoktur.


1
Bunu mongoda yapamayacağınızı düşünüyorum, belki eval'ü kullanarak çok fazla acı çekiyor. Mongo veri operasyonlarında çok sınırlıdır.
Antti Haapala

3
@Haapala: mongodb $ inc ve güncelleme ile upsert
jdi

1
@jdi evet evet, ama burada çok fazla yardımcı olmuyor, ancak ihtiyacı olan çoklu koşullu $ incs.
Antti Haapala

Yanıtlar:


238

Soru # 1 için, onu iki parçaya ayıralım. İlk olarak, "items.item_name" olan herhangi bir belgeyi "my_item_two" ya eşit olacak şekilde artırın. Bunun için konumsal "$" operatörünü kullanmanız gerekir. Gibi bir şey:

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

Bunun yalnızca herhangi bir dizideki ilk eşleşen alt belgeyi artıracağını unutmayın (bu nedenle dizide "öğe_adı" ile "my_item_two" değerine eşit başka bir belgeniz varsa, artırılmaz). Ama istediğin bu olabilir.

İkinci bölüm daha hileli. Aşağıdaki gibi "my_item_two" içermeyen bir diziye yeni bir öğe aktarabiliriz:

 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. sorunuz için cevap daha kolaydır. "My_item_three" içeren herhangi bir belgede item_three öğesinin toplamını ve fiyatını artırmak için, aynı anda birden çok alanda $ inc operatörünü kullanabilirsiniz. Gibi bir şey:

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

Cevap için teşekkürler. Soru # 2'nin cevabı mükemmel ve iyi çalışıyor :) Ama Soru # 1 için sorun, belgeyi "items.item_name" ile değil "user_id" ile aramanız gerektiğidir. Arama sorgusunda diziyi belirtmediğim için bu durumda Konumsal İşleci kullanamıyorum. Ayrıca her iki parçanın da tek seferde yapılmasını istiyorum. (mümkünse)
Majid

Güzel örnekler. Cevabımdaki bağlantılardan bu yönü belirginleştirdiğimi hissettim, ama aradığı şey olmadığını söyleyerek direnmeye devam ettim. OP sadece birkaç $ inc nasıl yapılacağına dair örnekleri görmek için gerekli sanırım.
jdi

Soru # 1 için, her güncellemenin sorgu bitine user_id öğesini ekleyerek iki bölüm halinde yapabilmeniz gerekir (yanıtı buna göre değiştireceğim). Tek bir atışta mümkün olup olmadığı hakkında daha fazla düşünmek zorunda kalacak.
matulef

6
Güncelleme yöntemindeki 3. ve 4. parametreler nelerdir? ve neden?
skmahawar

5
@skmahawar, 3. ve 4. parametrelerle ilgili olarak, docs.mongodb.com/manual/reference/method/db.collection.update bu seçeneklerin sırasıyla "yukarı" ve "çoklu" için olduğunu belirtir. Upert için true olarak ayarlanırsa, sorgu ölçütleriyle eşleşen belge olmadığında yeni bir belge oluşturur. Varsayılan değer false'dur ve eşleşme bulunamadığında yeni bir belge eklemez. "Çoklu için Doğru olarak ayarlanırsa, sorgu ölçütlerini karşılayan birden çok belgeyi günceller. False olarak ayarlanırsa bir belgeyi günceller. Varsayılan değer yanlış.
Mike Strand

24

Bunu tek bir sorguda yapmanın bir yolu yoktur. Belgeyi ilk sorguda aramalısınız:

Belge varsa:

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

Başka

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

Koşul eklemeye gerek yok {$ne : "my_item_two" }.

Ayrıca çok iş parçacıklı ortamlarda, aynı anda yalnızca bir iş parçacığının ikinciyi (belge bulunmadıysa, ekleme kutusu) yürütebileceğine dikkat etmeniz gerekir, aksi takdirde yinelenen katıştırılmış belgeler eklenir.


1
Hayır, bunu tek bir sorguda yapmanın bir yolu var, yukarıya bakın
Martijn Scheffer

1
@MartijnScheffer, bunu bu iş parçacığının herhangi bir yerinde tek bir sorgu olarak yapmanın bir yolunu görmüyorum. "Cevap" bile bu cevaptaki gibi 2 sorgu kullanıyor. Bir şey mi kaçırıyorum? Ayrıca herhangi bir çoklu iş parçacığı sorunları önlemek için bir sorguda bunu yapmak için bir yol olduğunu umuyorum
dferraro 14:17

@Udit, öğeleri nasıl kullanabilirim. $. Fiyat koşul içinde? "yalnızca fiyat 0'dan büyükse artış" gibi bir koşul eklemeye çalıştığımda işe yaramıyor - temelde hiçbir şey güncellenmiyor. Bunu nerede kullanabilirim, yoksa bir şey mi kaçırıyorum? öğeler. $. fiyat: {$ gt: 0}
dferraro

bir şey yok olmalı :)
Martijn Scheffer

3

Biz kullanabilirsiniz $setnesne değerini güncellemek dosyalanmış iç içe geçmiş bir dizi güncellemek için operatöre

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.