【问题标题】:How do I update MongoDB document fields only if they don't exist?仅当 MongoDB 文档字段不存在时,如何更新它们?
【发布时间】:2014-09-09 13:42:39
【问题描述】:

我收集了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 这样的东西,如果它存在,它不会覆盖值。

【问题讨论】:

  • 实际上这不会覆盖并且实际上只会在您设置multi后将新密钥“添加”到“第一个”文档中。但你不知道吗?
  • Ofc 我知道 multi 并且我在查询中使用它。但我需要$add,而不是$set
  • 您介意详细说明您的示例吗?您在搜索 $push 或 $addToSet 之类的东西吗?

标签: mongodb


【解决方案1】:

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

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

【讨论】:

  • 我想在单个查询中完成。
  • 仅供参考,如果您是 PHP 开发人员,可以在此处阅读有关示例的更多信息:php.net/manual/en/mongocollection.update.php
  • 如果想更新以键入相同的原始然后 .update({'qwerty':{$exists:false}, 'content': {$exists : false}}, {$set: { 'content': '','qwerty':''}}, {multi: true})
  • @Nitin,我认为这个多语句有问题;它只会更新 all 的指定字段不存在的文档。可以在此处使用$or,但随后所有字段都将被覆盖在其中一个不存在的任何文档中。我也在试图找到一个“单一操作”的解决方案......
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 完美!我不知道这存在。这似乎正是 OP 所寻找的
    • 注意:如果您想为现有文档设置新字段,这不起作用。如果在 upsert 期间没有插入新文档,setOnInsert 什么都不做
    • @Aarjav 我完全同意 - 我实际上需要你刚才提到的功能,我相信 OP 正在寻找它。你知道实现它的方法吗?
    • @Corbfon 不幸的是,我认为没有办法解决多个更新操作。一个可能但不太优雅的解决方法可能是使用 $min 或 $max 运算符,但它们在大多数情况下可能不起作用。 docs.mongodb.com/manual/reference/bson-type-comparison-order/…
    • @Aarjav 令人失望 :( 我只会打一个电话来获取有问题的文档,让服务在逻辑上更新正确的字段,然后一次保存所有内容。2 db ops vs > 5
    【解决方案4】:

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

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

    【讨论】:

    • 正是我需要更新现有文档的情况下所需要的
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      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.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 },否则只会更新第一个匹配的文档。

      【讨论】:

      • 谢谢!正是我想要的。
      【解决方案7】:

      如果有人像我一样遇到问题:

      我的解决方案是仅在更新导致插入新的 on (upsert = true) 时才设置 _id

      【讨论】:

        【解决方案8】:

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

        updateMany 一次更新。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-09-25
          • 1970-01-01
          • 2021-04-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多