假设我们有以下集合,我对此几乎没有疑问:
{
"_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
}
]
}
我想提高 "item_name":"my_item_two" 的价格,如果它不存在,则应将其附加到 "items" 数组中。如何同时更新两个字段?例如,增加“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,让我们将其分为两部分。首先,增加任何“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);
在单个查询中无法做到这一点。您必须在第一个查询中搜索文档:
如果文件存在:
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”字段不重复的一种方法是执行与问题 #1 的答案 https://stackoverflow.com/a/10523963 中给出的相同操作,但顺序相反!
如果 "items.item_name":{"$ne":"my_name"} 进入文档 - 更新过滤器必须包含一些唯一索引字段!而 upsert 是错误的。如果“items.item_name”:“my_name”,则增加文档。
第一次更新应该是原子的,因此如果数组已经包含 item_name 为“my_name”的元素,它不会做任何事情。
到第二次更新发生时,必须有一个带有“item_name”=“my_name”的数组元素