2012-05-09 14 views
143

Załóżmy, że mamy następującą kolekcję, która mam kilka pytań:MongoDB - Aktualizacja obiektów w dokumencie za array (zagnieżdżonego przeredagowywania)

{ 
    "_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 - Chcę, aby zwiększyć cenę za „ITEM_NAME”:” my_item_two ", a jeśli nie istnieje, należy go dołączyć do tablicy" items ".

2 - Jak zaktualizować dwa pola w tym samym czasie. Na przykład, zwiększ cenę za "my_item_three" i jednocześnie zwiększ "total" (z tą samą wartością).

Wolę robić to po stronie MongoDB, w przeciwnym razie muszę załadować dokument po stronie klienta (Python) i skonstruować zaktualizowany dokument i zastąpić go istniejącym w MongoDB.

UPDATE To co próbowałem i działa dobrze IF obiekt istnieje:

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

Ale jeśli klucz nie istnieje to nic nie robi. Również aktualizuje tylko zagnieżdżony obiekt. Za pomocą tego polecenia nie można również zaktualizować pola "suma".

+1

Myślę, że nie można tego zrobić w Mongo, chyba że z dużym bólem przy użyciu eval. Mongo jest bardzo ograniczone w opcjach danych. –

+3

@Haapala: mongodb ma $ inc i aktualizuje z upsert – jdi

+1

@jdi tak tak, ale to nie pomaga tutaj wiele, ale to, czego potrzebuje to wiele $ incs, warunkowo, a jeśli element nie istnieje, to $ push jest potrzebne. –

Odpowiedz

173

W przypadku pytania nr 1 podzielmy je na dwie części. Najpierw zwiększ każdy dokument o "items.item_name" równym "my_item_two". W tym celu będziesz musiał użyć pozycji "$" operatora. Coś jak:

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

pamiętać, że ten zacznie narastać tylko pierwszy dopasowany dokument podrzędny w każdej tablicy (więc jeśli masz inny dokument w tablicy z „nazwa_pozycji” równy „my_item_two”, to nie dostanie zwiększany) . Ale to może być to, czego chcesz.

Druga część jest trudniejsza. Możemy wcisnąć nowy element do tablicy bez „my_item_two” w następujący sposób:

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

za pytanie nr 2, odpowiedź jest łatwiejsze. Aby zwiększyć łączną i cenę item_three w dowolnym dokumencie zawierającym "my_item_three", możesz użyć operatora $ inc na wielu polach w tym samym czasie. Coś takiego:

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

Dzięki za odpowiedź. Odpowiedź na pytanie nr 2 jest idealna i działa dobrze :) Ale na pytanie nr 1 problem polega na wyszukiwaniu dokumentu przez "user_id", a nie przez "items.item_name". W tym przypadku nie mogę użyć operatora pozycji, ponieważ nie określiłem tablicy w zapytaniu. Również chcę, aby obie części zostały wykonane za jednym razem. (jeśli to możliwe) – Majid

+0

Dobre przykłady. Czułem się, jakbym robił to w sposób oczywisty z linków w mojej odpowiedzi, ale ciągle stawałem opór, mówiąc, że nie to, czego szukał. Zgadnij, OP tylko potrzebne, aby zobaczyć przykłady, jak zrobić wiele $ inc. – jdi

+0

Dla pytania # 1 powinieneś być w stanie zrobić to w dwóch częściach, dodając identyfikator użytkownika do bitu kwerendy każdej aktualizacji (ja odpowiednio zmienię odpowiedź). Będą musieli zastanowić się, czy można to zrobić za jednym razem. – matulef

19

Nie ma sposobu, aby zrobić to w pojedynczej kwerendzie. Trzeba szukać dokument w pierwszej kwerendy:

Jeśli dokument istnieje:

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

Nie trzeba dodawać stan {$ne : "my_item_two" }.

Również w środowisku wielowątkowym należy uważać, aby tylko jeden wątek mógł wykonać drugi (w przypadku, gdy dokument nie został znaleziony), w przeciwnym razie zostaną wstawione duplikaty dokumentów osadzonych.

+1

Nie, jest sposób, aby to zrobić w pojedynczym zapytaniu, patrz powyżej –

+0

@MartijnScheffer, Nie widzę sposobu, aby to zrobić jako pojedynczego zapytania w dowolnym miejscu w tym wątku. Nawet "odpowiedź" używa dwóch zapytań takich samych jak w tej odpowiedzi. Czy czegoś brakuje? Mam również nadzieję, że istnieje sposób, aby to zrobić w jednym zapytaniu, aby zapobiec problemom z wieloma wątkami. – dferraro

+0

@Udit, w jaki sposób mogę użyć przedmiotów. $. Cena wewnątrz warunku? kiedy próbuję dodać warunek taki jak "przyrost tylko wtedy, gdy cena jest większa niż 0", to nie działa - w zasadzie nic się nie aktualizuje. Mogę użyć tego w warunku, czy coś mi brakuje? items. $. price: {$ gt: 0} – dferraro

Powiązane problemy