如何更新 MongoDB 文档字段,如果它们不存在?

我收集了 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'}}

我需要更新多个可能存在或不存在的字段:

db.foo.update(
{ site_id: 'xxx'},
{ $set: {'title.de': '', 'content.de': ''}},
{multi: true}
)

但是我需要像 $set这样的东西,它不会覆盖值,如果它存在。

103394 次浏览

可以在更新语句中添加查询:

db.foo.update({'title.de': {$exists : false}}, {$set: {'title.de': ''}})

更新

对于你修改后的问题,我的解决方案是这样的——这对你有用吗? (如果没有,为什么?)

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

尽管给出的答案基本上概述了这种方法,但是由于 MongoDB 2.6或更高版本的实现支持“批量更新”,因此您可以使用 MongoDB 2.6或更高版本进行此类操作。

这仍然是“从原子角度来说”的单独更新语句。但你可以一次性提交。这至少可以确保在服务器上执行更新时,更新之间的延迟要短得多:

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();

所以这实际上是一个到服务器的往返过程,因为所有东西都只在 .execute()上发送

但是在您目前的格式中(尽管这可能不是数据的准确表示) ,您实际上可以“重新构造”,以便在单个操作中完成这项工作。如果你的文件是这样的:

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

然后按照 强 > $addToSet的规则执行以下操作,其中“ set”元素将是“惟一的”:

db.foo.update(
{ "site_id": "xxx" },
{ "$addToSet": { "docs": { "title": "d", content: "d", "lang": "de" } } },
{ "multi": true }
)

或者即使没有逻辑,只是检查存在:

db.foo.update(
{ "site_id": "xxx", "docs.lang": { "$ne": "de" } },
{ "$push": { "docs": { "title": "", "content": "", "lang": "de" } } },
{ "multi": true }

)

最后一种情况会导致这样的结果:

{
"_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"
}
]
}

因此,选择是要么以不同的方式“处理”事情,要么只是更改模式,以适应您希望原子化地执行的那种更新。

有一个更新字段操作符 $setOnInsert 满足您的要求。请阅读这里的文档: https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert

我有一个特殊案例的解决方案,但可能对某些人有帮助。

我的情况是: 更新几个字段,其中有一个字段只需要更新一次(我们称之为“ 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") }

@ nutlike 的回答确实解决了这个问题,但是,如果您想要更新该项的多个字段,则需要进行许多数据库操作。简而言之,你想要的是不可能的。

如果你的文档有更多的更新要做,比你想做的一次(任何大于2,IMO) ,那么你应该只是得到文档,更新字段,然后保存它。这是我在几个 OAuth 用户创建/更新路由时所做的工作。

Mongo 4.2开始,db.collection.update()可以接受聚合管道,最终允许基于另一个字段更新/创建字段:

这样,我们可以将字段检查移动到更新阶段而不是匹配阶段,从而使其成为一次性更新:

// { site_id: "xxx", title: { ru: "a", en: "b" }, content: {} }
// { site_id: "xxx", title: { ru: "c", de: "d" }, content: { ru: "c" } }
db.collection.updateMany(
{ site_id: "xxx" },
[{ $set: {
"title.de": { $cond: [ { $not: ["$title.de"] }, "", "$title.de" ] },
"content.ru": { $cond: [ { $not: ["$content.ru"] }, "", "$content.ru" ] }
}}]
)
// { site_id: "xxx", title: { ru: "a", en: "b", de: "" }, content: { ru: "" } }
// { site_id: "xxx", title: { ru: "c", de: "d"         }, content: { ru: "c" } }
  • 第一部分 { site_id: "xxx" }是匹配查询,过滤要更新的文档。

  • 第二部分 [{ $set: { ... } }]是更新聚合管道(注意表示使用聚合管道的方括号)。$set是一个新的聚合运算符和 $addFields的别名。此阶段的其余部分检查 $cond是否存在 title.de,如果存在,则保持其原样,或以其他方式创建 ''

如果有人像我一样遇到这个问题: enter image description here

我的解决方案是仅当更新导致插入 new on (upsert: true)时才设置 _ id

return {
updateOne: {
filter: {
email: shadowUser.email,
},
update: {
$set: user,
$setOnInsert: { _id: shadowUser._id },
},
upsert: true,
},
};

由于不推荐使用 update查询,因此根据您的要求使用 updateOneupdateMany

同时更新 updateMany

db.foo.updateMany({'title.de': {$exists : false}}, {$set: {'title.de': ''}})

//如果字段不存在,则在所有文档中添加具有某些值的字段, 在这里,我要说的是,如果 lob字段不在所有文档中,则添加 lob: 'Marine'

db.collections.updateMany({'lob': { $exists: false }}, { $set: { lob: 'Marine'}});