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

时间:2014-07-18 12:07:55

标签: 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这样的东西,如果它存在,它将不会覆盖值。

7 个答案:

答案 0 :(得分:41)

您可以向更新语句添加查询:

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

答案 1 :(得分:8)

有一个更新字段运算符$ setOnInsert,它符合您的要求。请在此处阅读文档:https://docs.mongodb.com/manual/reference/operator/update/setOnInsert/#up._S_setOnInsert

答案 2 :(得分:6)

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

我的案例是: 更新几个字段,其中一个字段必须只更新一次(让我们称之为" 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") } 

答案 3 :(得分:1)

尽管给出的答案基本上概述了这种方法,但由于支持“批量更新”的实现,你可以使用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"
            }
    ]
}

因此,选择是以不同方式“处理”事物,或者只是更改您的架构以适应您想要原子化的更新类型。

答案 4 :(得分:0)

@ nutlike的答案确实解决了这个问题,但是,如果你想更新项目上的多个字段,它将需要许多数据库操作。简而言之,你想要的并不是完全可能的。

如果您的文档有更多要做的更新,而不是一次做一个(任何大于2,IMO),那么您应该只获取文档,更新字段然后保存。这就是我在几个OAuth用户创建/更新路由时所做的。

答案 5 :(得分:0)

Mongo 4.2db.collection.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" } }
  • 第一部分{ site_id: "xxx" }是匹配查询,用于过滤要更新的文档。

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

  • 不要忘记{ multi: true },否则只会更新第一个匹配的文档。

答案 6 :(得分:0)

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

我的解决方案是仅当更新导致插入新的时才设置 _id (upsert = true) enter image description here