2012-05-09 90 views
143

假設我們有以下的集合,我有幾個問題:MongoDB的 - 在一個文檔的數組(嵌套更新)更新對象

{ 
    "_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中現有的更換。

UPDATE 這是我曾嘗試和如果對象存在工作正常

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

但是,如果該鍵不存在,它什麼都不做。 此外它只更新嵌套的對象。此命令也無法更新「總計」字段。

+1

我覺得有太多的痛苦使用eval你不能做到這一點蒙戈,也許除了。 Mongo在數據操作中非常有限。 –

+3

@Haapala:mongodb有$ inc並且用upsert更新 – jdi

+1

@jdi是的,但這對這裏沒有多大幫助,但是他需要的是多個$ incs,有條件的,如果該項不存在,那麼$ push是需要。 –

回答

173

對於問題#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,答案是更容易。要在包含「my_item_three」的任何文檔中增加item_three的總數和價格,可以同時在多個字段上使用$ inc操作符。例如:

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

感謝您的回覆。 問題#2的答案是完美的,並且工作正常:) 但對於問題1,問題是您應該通過「user_id」而不是「items.item_name」來搜索文檔。 在這種情況下,我不能使用位置運算符,因爲我沒有在搜索查詢中指定數組。另外我希望兩部分都能一次完成。 (如果可能) – Majid

+0

不錯的例子。我覺得我正在從我的答案中的鏈接中明確指出這個方向,但我不斷遇到阻力,並不是他想要的。猜測OP只需查看關於如何執行多個$ inc的示例。 – jdi

+0

對於問題#1,您應該仍然可以通過將user_id添加到每個更新的查詢位(我將相應地修改答案),將它分爲兩​​部分。將不得不更多地考慮一下是否可以做到這一點。 – matulef

19

在單個查詢中沒有辦法做到這一點。你必須尋找在第一個查詢文檔:

如果文件存在:

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

不,有一種方法可以在單個查詢中執行此操作,請參閱上面的 –

+0

@MartijnScheffer,我沒有看到一種方法可以在此線程的任何位置將其作爲單個查詢來執行此操作。即使是「答案」,也會使用與本答案中相同的2個查詢。我錯過了什麼嗎?我也希望有一種方法可以在一個查詢中做到這一點,以防止任何多線程問題 – dferraro

+0

@Udit,我怎樣才能使用項目。當我嘗試添加諸如「僅在價格大於0時增加」之類的條件時,它才起作用 - 基本上沒有任何更新。我可以在哪裏使用這個條件,或者我錯過了什麼? items。$。price:{$ gt:0} – dferraro