MongoDB-更新文档数组中的对象(嵌套更新)


204

假设我们有以下收藏,对此我有几个疑问:

{
    "_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”的价格,同时增加“ total”(具有相同的值)。

我更喜欢在MongoDB上执行此操作,否则,我必须在客户端(Python)中加载文档并构造更新的文档,并用MongoDB中的现有文档替换它。

更新 这是我尝试过的并且如果对象存在则可以正常工作:

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

但是,如果密钥不存在,则不会执行任何操作。同样,它仅更新嵌套对象。此命令也无法更新“总计”字段。


1
我认为您无法在mongo中执行此操作,除非使用评估功能可能会很痛苦。Mongo的数据操作非常有限。
Antti Haapala'5

3
@Haapala:mongodb具有$ inc并使用upsert更新
jdi 2012年

1
@jdi是是,但是在这里没有太大帮助,但是他需要的是有条件的多个$ incs,如果该项目不存在,则需要一个$ push。
Antti Haapala'5

Answers:


237

对于问题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);

谢谢回复。问题2的答案是完美的,并且效果很好:)但是对于问题1,问题是您应该通过“ user_id”而不是“ items.item_name”搜索文档。在这种情况下,我无法使用位置运算符,因为我没有在搜索查询中指定数组。我也希望两个部分都可以一次完成。(如果可能的话)
2012年

很好的例子。我觉得我从答案中的链接中可以清楚地看出这个方向,但是我一直在反抗,说这不是他在寻找什么。猜猜OP仅需要查看有关如何执行多个$ inc的示例。
jdi 2012年

对于问题1,您仍然应该能够通过将user_id添加到每个更新的查询位中来分两部分进行操作(我将相应地修改答案)。将不得不更多地考虑是否有可能一次完成。
matulef

6
更新方法中的第三和第四参数是什么?为什么呢?
skmahawar 2015年

5
关于第三和第四参数的@ skmahawardocs.mongodb.com / manual / reference / method / db.collection.update指示这些选项分别用于“ upsert”和“ multi”。对于upsert,如果设置为true,则在没有文档符合查询条件时创建一个新文档。默认值为false,如果找不到匹配项,则不插入新文档。”对于multi,如果设置为true,则更新多个符合查询条件的文档。如果设置为false,则更新一个文档。默认值为错误
Mike Strand

24

无法在单个查询中执行此操作。您必须在第一个查询中搜索文档:

如果文档存在:

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
不,有一种方法可以在单个查询中执行此操作,请参见上文
Martijn Scheffer

1
@MartijnScheffer,在该线程中的任何地方,我都看不到作为单个查询来执行此操作的方法。甚至“答案”也使用2个与此答案相同的查询。我想念什么吗?我也希望有一种方法可以在一个查询中执行此操作以防止任何多线程问题
dferraro

@Udit,如何在条件内使用items。$。price?当我尝试添加诸如“仅当价格大于0时才增加”之类的条件时,它不起作用-基本上没有任何更新。我可以在where条件下使用它,还是缺少某些东西?items。$。price:{$ gt:0}
dferraro

某些东西必须消失了:)
马丁·谢弗

3

我们可以使用$set运算符来更新对象字段中的嵌套数组来更新值

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.