仅当 MongoDB 文档字段不存在时,如何更新它们?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24824657/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I update MongoDB document fields only if they don't exist?
提问by Miraage
I have collection foo
with documents like:
我收集foo
了以下文件:
{site_id: 'xxx', title: {ru: 'a', en: 'b'}, content: {ru: 'a', en: 'b'}}
{site_id: 'xxx', title: {ru: 'c', de: 'd'}, content: {ru: 'c', de: 'd'}}
I need to update multiple fields which are can exists or not:
我需要更新可以存在或不存在的多个字段:
db.foo.update(
{ site_id: 'xxx'},
{ $set: {'title.de': '', 'content.de': ''}},
{multi: true}
)
But I need something like $set
which will not overwrite value if it exists.
但我需要类似的东西$set
,如果它存在就不会覆盖价值。
回答by nutlike
You can add a query to your update statement:
您可以向更新语句添加查询:
db.foo.update({'title.de': {$exists : false}}, {$set: {'title.de': ''}})
Update
更新
For your modified question my solution looks like this - would that work for you? (If not, why?)
对于您修改后的问题,我的解决方案如下所示 - 这对您有用吗?(如果不是,为什么?)
db.foo.update({site_id: 'xxx', 'title.de': {$exists : false}}, {$set: {'title.de': ''}, {multi: true})
db.foo.update({site_id: 'xxx', 'content.de': {$exists : false}}, {$set: {'content.de': ''}}, {multi: true})
回答by Bing Wu
there is a update field operator $setOnInsert which meets your requirement. Please read the documents here: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert
有一个更新字段运算符 $setOnInsert 可以满足您的要求。请在此处阅读文档:https: //docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert
回答by hi_artem
I have a solution for one particular case, but may be it helps someone.
我有一个针对一个特定案例的解决方案,但可能对某人有帮助。
My case was: Update several fields, among which was a field that had to be updated only once (lets call it "Date_of_first_update").
我的情况是:更新几个字段,其中一个字段必须只更新一次(我们称之为“Date_of_first_update”)。
> db.test.find();
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : "1", "b" : "2" }
First update:
> db.test.updateOne({ "_id" : ObjectId("57f298fdeb30478a033c70e4")},
{$set: {a: 100, b: 200 }, $min : {'Date_of_first_update' : (new Date()) }});
Result: 'a', 'b' updated, 'Date_of_first_update' is set.
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : 100, "b" : 200, "Date_of_first_update" : ISODate("2016-10-03T**17:47:43**.570Z") }
Second update:
> db.test.updateOne({ "_id" : ObjectId("57f298fdeb30478a033c70e4")},
{$set: {a: 400, b: 800 }, $min : {'Date_of_first_update' : (new Date()) }});
Result: 'a', 'b' updated, 'Date_of_first_update' left unchanged, as I needed!!!
{ "_id" : ObjectId("57f298fdeb30478a033c70e4"), "a" : 400, "b" : 800, "Date_of_first_update" : ISODate("2016-10-03T**17:47:43**.570Z") }
回答by Xavier Guihot
Starting Mongo 4.2
, db.collection.update()
can accept an aggregation pipeline, finally allowing the update/creation of a field based on another field:
开始Mongo 4.2
,db.collection.update()
可以接受聚合管道,最后允许基于另一个字段更新/创建一个字段:
This way, we can move field checks within the update stage rather than within the match stage, thus making it a one-pass update:
这样,我们可以在更新阶段而不是在匹配阶段移动字段检查,从而使其成为一次性更新:
// { site_id: "xxx", title: { ru: "a", en: "b" }, content: {} }
// { site_id: "xxx", title: { ru: "c", de: "d" }, content: { ru: "c" } }
db.collection.update(
{ site_id: "xxx" },
[{ $set: {
"title.de": { $cond: [ { $not: ["$title.de"] }, "", "$title.de" ] },
"content.ru": { $cond: [ { $not: ["$content.ru"] }, "", "$content.ru" ] }
}}],
{ multi: true }
)
// { site_id: "xxx", title: { ru: "a", en: "b", de: "" }, content: { ru: "" } }
// { site_id: "xxx", title: { ru: "c", de: "d" }, content: { ru: "c" } }
The first part
{ site_id: "xxx" }
is the match query, filtering which documents to update.The second part
[{ $set: { ... } }]
is the update aggregation pipeline (note the squared brackets signifying the use of an aggregation pipeline).$set
is a new aggregation operator and an alias of$addFields
. The rest of this stage checks with$cond
if thetitle.de
exists, and if yes, then keep it as it is, or otherwise create it as an''
.Don't forget
{ multi: true }
, otherwise only the first matching document will be updated.
第一部分
{ site_id: "xxx" }
是匹配查询,过滤要更新的文档。第二部分
[{ $set: { ... } }]
是更新聚合管道(注意方括号表示使用聚合管道)。$set
是一个新的聚合运算符,是 的别名$addFields
。此阶段的其余部分检查$cond
是否title.de
存在,如果存在,则保持原样,否则将其创建为''
.不要忘记
{ multi: true }
,否则只会更新第一个匹配的文档。
回答by Neil Lunn
Even though the answer given essentially outlines the approach, you can do this sort of thing with MongoDB 2.6 or greater due to the implementation there that supports "bulk updates".
尽管给出的答案基本上概述了该方法,但由于支持“批量更新”的实现,您可以使用 MongoDB 2.6 或更高版本执行此类操作。
This are still, "atomically speaking", separate update statements. But you can submit them "over the wire" in one go. Which at least makes sure that the latency between the updates is much shorter as they are executed on the server:
这仍然是“从原子上讲”,单独的更新语句。但是您可以一次性“通过网络”提交它们。这至少可以确保更新之间的延迟在服务器上执行时要短得多:
var bulk = db.foo.initializeBulkOrderedOp();
bulk.find({ "site_id": "xxx",
"title.de": { "$exists" false } })
.update({ "$set": { "title.de": "" } });
bulk.find({ "site_id": "xxx",
"content.de": { "$exists" false } })
.update({ "$set": { "content.de": "" } });
bulk.execute();
So that is actually one round trip to the server as everything only sends on .execute()
所以这实际上是到服务器的一次往返,因为一切都只发送 .execute()
But in your present form (though this may not be a accurate representation of your data), you can actually "re-structure" in order to do this in a single operation. So if your documents looked like this:
但是在您目前的形式中(尽管这可能不是您数据的准确表示),您实际上可以“重新构建”以便在单个操作中完成此操作。因此,如果您的文档如下所示:
{
"site_id": "xxx",
"docs": [
{ "title": "a", "content": "a", "lang": "ru" },
{ "title": "b", "content": "b", "lang": "en" }
]
},
{
"site_id": "xxx",
"docs": [
{ "title": "c", "content": "c", "lang": "ru" },
{ "title": "d", "content": "d", "lang": "de" }
]
}
Then the following works by the rule of $addToSet
where the "set" element would be "unique":
然后根据$addToSet
“set”元素“唯一”的规则进行以下工作:
db.foo.update(
{ "site_id": "xxx" },
{ "$addToSet": { "docs": { "title": "d", content: "d", "lang": "de" } } },
{ "multi": true }
)
Or even without the logic there and just checking for presence:
或者甚至没有逻辑,只是检查存在:
db.foo.update(
{ "site_id": "xxx", "docs.lang": { "$ne": "de" } },
{ "$push": { "docs": { "title": "", "content": "", "lang": "de" } } },
{ "multi": true }
)
)
Which in that last case would result in this:
在最后一种情况下会导致:
{
"_id" : ObjectId("53c936265117367f5ff2038b"),
"site_id" : "xxx",
"docs" : [
{
"title" : "a",
"content" : "a",
"lang" : "ru"
},
{
"title" : "b",
"content" : "b",
"lang" : "en"
},
{
"title" : "",
"content" : "",
"lang" : "de"
}
]
}
{
"_id" : ObjectId("53c936265117367f5ff2038c"),
"site_id" : "xxx",
"docs" : [
{
"title" : "c",
"content" : "c",
"lang" : "ru"
},
{
"title" : "d",
"content" : "d",
"lang" : "de"
}
]
}
So the choice is there to either "handle" things differently or otherwise just change your schema to accommodate the sort of updates you want to do atomically.
因此,您可以选择以不同的方式“处理”事情,或者只是更改您的架构以适应您想要以原子方式进行的更新类型。
回答by Corbfon
@nutlike's answer does solve the issue, however, if you would like to update multiple fields on the item it will require many database operations. In short, what you want is not exactly possible.
@nutlike 的回答确实解决了这个问题,但是,如果您想更新项目上的多个字段,它将需要许多数据库操作。简而言之,您想要的并不完全可能。
If your doc has more updates to do than you'd like to do one at a time (any greater than 2, IMO), then you should just get the document, update the fields and then save it. This is what I do on a couple of OAuth user creating/updating routes.
如果您的文档要进行的更新比您希望一次更新的要多(任何大于 2,IMO),那么您应该只获取文档,更新字段,然后保存它。这就是我在几个 OAuth 用户创建/更新路由时所做的。