如何更新文档数组中的对象(嵌套更新)

假设我们有以下收集,我有几个问题:

{
"_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”;如果它不存在,它应该被添加到"item "数组中。

  2. 如何同时更新两个字段?例如,提高“my_item_three”的价格;同时增加“总数”;(值相同)。

我更喜欢在MongoDB端这样做,否则我必须在客户端(Python)加载文档,并构造更新的文档,并将其替换为MongoDB中的现有文档。

这是我尝试过的,工作良好如果对象存在:

db.test_invoice.update({user_id : 123456 , "items.item_name":"my_item_one"} , {$inc: {"items.$.price": 10}})
但是,如果键不存在,则不执行任何操作。 而且,它只更新嵌套对象。使用此命令无法更新“total”;

248131 次浏览

对于第一个问题,让我们把它分成两部分。首先,增加任何具有“项”的文档。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);

在单个查询中无法做到这一点。你必须在第一次查询中搜索文档:

如果文档存在:

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" }

此外,在多线程环境中,你必须小心,一次只有一个线程可以执行第二个(插入情况,如果没有找到文档),否则将插入重复的嵌入文档。

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

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

确保“item_name”没有重复的方法之一;字段将执行与第一个问题的答案https://stackoverflow.com/a/10523963相同的操作,但顺序相反!

  1. 如果“;items.item_name":{"$ne":"my_name"}”,则推入文档-更新过滤器必须包含一些唯一索引字段!upsert为false。
  2. 如果&;items.item_name&;:&;my_name&;;

第一次更新应该是原子的,因此如果数组已经包含item_name "my_name"的元素,它不会做任何事情。

第二次更新发生时,必须有一个数组元素的“item_name”;=“my_name”;