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

Maj*_*jid 193 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
            }
    ]
}
Run Code Online (Sandbox Code Playgroud)

1 -我要加大对"ITEM_NAME"的价格:"my_item_two" ,如果不存在的话,它应该被追加到"项"阵列.

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}})
Run Code Online (Sandbox Code Playgroud)

但如果密钥不存在则无效.它也只更新嵌套对象.此命令无法更新"total"字段.

mat*_*lef 229

对于问题#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);
Run Code Online (Sandbox Code Playgroud)

请注意,这只会增加任何数组中第一个匹配的子文档(因此,如果数组中的另一个文档的"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);
Run Code Online (Sandbox Code Playgroud)

对于你的问题#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);
Run Code Online (Sandbox Code Playgroud)

  • 更新方法中的第3和第4个参数是什么?为什么? (5认同)
  • @skmahawar关于第3和第4个参数,https://docs.mongodb.com/manual/reference/method/db.collection.update/表示这些选项分别用于"upsert"和"multi".对于upsert,如果设置为true,则在没有文档与查询条件匹配时创建新文档.默认值为false,在未找到匹配项时不插入新文档."对于multi,如果设置为true,则更新满足查询条件的多个文档.如果设置为false,则更新一个文档.默认值为假. (5认同)

小智 24

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

如果文件存在:

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

其他

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

无需添加条件{$ne : "my_item_two" }.

同样在多线程环境中,您必须要小心,一次只有一个线程可以执行第二个(插入案例,如果没有找到文档),否则将插入重复的嵌入文档.

  • 不,有一种方法可以在单个查询中执行此操作,请参见上文 (2认同)
  • @MartijnScheffer,我在此线程中的任何地方都没有看到将其作为单个查询执行此操作的方法。甚至“答案”也使用与此答案相同的 2 个查询。我错过了什么吗?我也希望有一种方法可以在一个查询中做到这一点,以防止任何多线程问题 (2认同)

KAR*_*N.A 7

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

db.getCollection('geolocations').update( 
   {
       "_id" : ObjectId("5bd3013ac714ea4959f80115"), 
       "geolocation.country" : "United States of America"
   }, 
   { $set: 
       {
           "geolocation.$.country" : "USA"
       } 
    }, 
   false,
   true
);
Run Code Online (Sandbox Code Playgroud)