2012-05-09 91 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