【问题标题】:How do I partially update an object in MongoDB so the new object will overlay / merge with the existing one如何部分更新 MongoDB 中的对象,以便新对象与现有对象重叠/合并
【发布时间】:2012-05-04 15:49:35
【问题描述】:

鉴于此文档保存在 MongoDB 中

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
   }
}

需要保存具有来自外界的param2 和param3 的新信息的对象

var new_info = {
    param2 : "val2_new",
    param3 : "val3_new"
};

我想在对象的现有状态上合并/覆盖新字段,这样 param1 就不会被删除

这样做

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

将导致 MongoDB 完全按照要求执行,并将 some_key 设置为该值。替换旧的。

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}

让 MongoDB 只更新新字段的方法是什么(没有明确地一一说明)?得到这个:

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2_new",
        param3 : "val3_new"
   }
}

我正在使用 Java 客户端,但任何示例都将不胜感激

【问题讨论】:

标签: mongodb mongodb-java


【解决方案1】:

我用自己的函数解决了这个问题。如果您想更新文档中的指定字段,您需要清楚地解决它。

例子:

{
    _id : ...,
    some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
    }
}

如果你只想更新param2,这样做是错误的:

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } }  //WRONG

你必须使用:

db.collection.update(  { _id:...} , { $set: { some_key.param2 : new_info  } } 

所以我写了一个类似的函数:

function _update($id, $data, $options=array()){

    $temp = array();
    foreach($data as $key => $value)
    {
        $temp["some_key.".$key] = $value;
    } 

    $collection->update(
        array('_id' => $id),
        array('$set' => $temp)
    );

}

_update('1', array('param2' => 'some data'));

【讨论】:

  • 这应该是公认的答案。如需更好的 flattenObject 函数,请参阅gist.github.com/penguinboy/762197
  • 感谢您的回复帮助指导我。我能够修改用户集合中用户的特定键的值,如下所示:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
  • 这个操作是原子的和孤立的吗?这意味着,如果 2 个并行线程尝试设置同一文档的 2 个不同字段,这会导致竞争条件和不可预测的结果
  • 现在可以在更新查询中使用 4.2 聚合支持 - 查看我的答案 - stackoverflow.com/a/65007307/2683814
【解决方案2】:

如果我正确理解了这个问题,您想用另一个文档的内容更新一个文档,但只更新尚未存在的字段,并完全忽略已经设置的字段(即使是另一个值)。

没有办法在单个命令中做到这一点。

您必须先查询文档,找出您想要 $set 的内容,然后对其进行更新(使用旧值作为匹配过滤器,以确保您不会在两者之间获得并发更新)。


您的问题的另一种解读是您对$set 感到满意,但不想明确设置所有字段。那么你将如何传递数据呢?

您知道您可以执行以下操作:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 

【讨论】:

  • 如果要无条件设置所有字段,可以使用$multi参数,像这样:db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
  • 没有 $multi 参数。有一个多选项(这是您链接到的),但这不会影响字段的更新方式。它控制您是更新单个文档,还是更新与查询参数匹配的所有文档。
  • 这没有任何意义。在处理 mongo 时,更新也最好具有原子操作。每当其他人更改您的数据时,第一次读取它会导致脏读。而且由于 mongo 没有交易,它会很乐意为您破坏数据。
  • @froginvasion:您可以通过使用旧值作为更新的匹配过滤器使其成为原子。这样,如果同时存在冲突的并发更新,您的更新将失败。然后,您可以检测到并采取相应措施。这称为乐观锁定。但是,是的,这取决于您的应用程序是否正确实施,Mongo 本身没有内置事务。
  • 我认为这是在 javascript 中合并两个对象的更普遍的问题。 IIRC 的想法是简单地将新的属性分配给旧的
【解决方案3】:

您可以使用点符号来访问和设置对象深处的字段,而不会影响这些对象的其他属性。

鉴于您在上面指定的对象:

> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })

我们可以只更新some_key.param2 和some_key.param3:

> db.test.findAndModify({
... query: {"id": "test_object"},
... update: {"$set": {"some_key.param2": "val2_new", "some_key.param3": "val3_new"}},
... new: true
... })
{
    "_id" : ObjectId("56476e04e5f19d86ece5b81d"),
    "id" : "test_object",
    "some_key" : {
        "param1" : "val1",
        "param2" : "val2_new",
        "param3" : "val3_new"
    }
}

您可以随心所欲地深入研究。这对于向对象添加新属性而不影响现有属性也很有用。

【讨论】:

  • 我可以添加一个新的键...比如“一些键”:{“param1”:“val1”,“param2”:“val2_new”,“param3”:“val3_new”,“param4 " : "val4_new", }
【解决方案4】:

最好的解决方案是从对象中提取属性并使其成为平面点符号键值对。例如,您可以使用这个库:

https://www.npmjs.com/package/mongo-dot-notation

它具有.flatten 功能,允许您将对象更改为一组属性,然后可以将其赋予 $set 修饰符,而不必担心现有数据库对象的任何属性将被删除/覆盖而无需。

取自mongo-dot-notation docs:

var person = {
  firstName: 'John',
  lastName: 'Doe',
  address: {
    city: 'NY',
    street: 'Eighth Avenu',
    number: 123
  }
};



var instructions = dot.flatten(person)
console.log(instructions);
/* 
{
  $set: {
    'firstName': 'John',
    'lastName': 'Doe',
    'address.city': 'NY',
    'address.street': 'Eighth Avenu',
    'address.number': 123
  }
}
*/

然后它形成完美的选择器——它只会更新给定的属性。 编辑:有时我喜欢成为考古学家;)

【讨论】:

    【解决方案5】:

    Mongo 允许您使用 . 约定更新嵌套文档。看看:Updating nested documents in mongodb。这是过去关于合并更新的另一个问题,我相信你正在寻找的那个:MongoDB atomic update via 'merge' document

    【讨论】:

      【解决方案6】:

      从Mongo 4.2开始,db.collection.updateMany()(或db.collection.update())可以接受一个聚合管道,它允许使用聚合操作符,例如$addFields,它输出输入文档中的所有现有字段和新添加的字段:

      var new_info = { param2: "val2_new", param3: "val3_new" }
      
      // { some_key: { param1: "val1", param2: "val2", param3: "val3" } }
      // { some_key: { param1: "val1", param2: "val2"                 } }
      db.collection.updateMany({}, [{ $addFields: { some_key: new_info } }])
      // { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
      // { some_key: { param1: "val1", param2: "val2_new", param3: "val3_new" } }
      
      • 第一部分{} 是匹配查询,过滤要更新的文档(在本例中为所有文档)。

      • 第二部分[{ $addFields: { some_key: new_info } }]是更新聚合管道:

        • 注意方括号表示使用聚合管道。
        • 由于这是一个聚合管道,我们可以使用$addFields。
        • $addFields 完全符合您的需求:更新对象,以便新对象与现有对象重叠/合并:
        • 在这种情况下,{ param2: "val2_new", param3: "val3_new" } 将合并到现有的 some_key 中,方法是保持 param1 不变,并添加或替换 param2 和 param3。

      【讨论】:

        【解决方案7】:

        我以这种方式成功地做到了:

        db.collection.update(  { _id:...} , { $set: { 'key.another_key' : new_info  } } );
        

        我有一个函数可以动态处理我的个人资料更新

        function update(prop, newVal) {
          const str = `profile.${prop}`;
          db.collection.update( { _id:...}, { $set: { [str]: newVal } } );
        }
        

        注意:'profile' 是特定于我的实现的,它只是您要修改的键的字符串。

        【讨论】:

          【解决方案8】:

          你宁愿做一个 upsert,这个操作在 MongoDB 中用于将文档保存到集合中。 如果文档符合查询条件,那么它将执行更新操作,否则它将向集合中插入一个新文档。

          类似下面的东西

          db.employees.update(
              {type:"FT"},
              {$set:{salary:200000}},
              {upsert : true}
           )
          

          【讨论】:

            【解决方案9】:
                // where clause DBObject
                DBObject query = new BasicDBObject("_id", new ObjectId(id));
            
                // modifications to be applied
                DBObject update = new BasicDBObject();
            
                // set new values
                update.put("$set", new BasicDBObject("param2","value2"));
            
               // update the document
                collection.update(query, update, true, false); //3rd param->upsertFlag, 4th param->updateMultiFlag
            

            如果您有多个字段要更新

                    Document doc = new Document();
                    doc.put("param2","value2");
                    doc.put("param3","value3");
                    update.put("$set", doc);
            

            【讨论】:

              【解决方案10】:
              db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 
              

              到

              db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } ); 
              

              希望对您有所帮助!

              【讨论】:

                【解决方案11】:

                您可以在基于聚合的更新中使用$mergeObjects。类似的东西

                db.collection.update(
                   { _id:...},
                   [{"$set":{
                      "some_key":{
                        "$mergeObjects":[
                          "$some_key",
                          new info or { param2 : "val2_new", param3 : "val3_new"}
                       ]
                      }
                   }}]
                )
                

                更多例子here

                【讨论】:

                • “将多个文档合并为一个文档。”
                【解决方案12】:

                看起来你可以设置isPartialObject,这可能会完成你想要的。

                【讨论】:

                • 试过了。如果我没记错的话,它不会让你保存部分对象......但是谢谢
                【解决方案13】:

                您必须使用嵌入式文档(将路径对象字符串化)

                let update = {}
                Object.getOwnPropertyNames(new_info).forEach(param => {
                   update['some_key.' + param] = new_info[param]
                })
                

                因此,在 JavaScript 中,您可以使用扩展运算符 (...) 来更新

                db.collection.update(  { _id:...} , { $set: { ...update  } } 
                

                【讨论】:

                  【解决方案14】:

                  是的,最好的方法是将对象表示法转换为平面键值字符串表示,如此评论中所述:https://stackoverflow.com/a/39357531/2529199

                  我想强调一个使用这个 NPM 库的替代方法:https://www.npmjs.com/package/dot-object,它允许您使用点符号来操作不同的对象。

                  当接受键值作为函数变量时,我使用此模式以编程方式创建嵌套对象属性,如下所示:

                  const dot = require('dot-object');
                  
                  function(docid, varname, varvalue){
                    let doc = dot.dot({
                        [varname]: varvalue 
                    });
                  
                    Mongo.update({_id:docid},{$set:doc});
                  }
                  

                  这种模式让我可以交替使用嵌套和单级属性,并将它们干净地插入到 Mongo 中。

                  如果您需要在 Mongo 之外使用 JS 对象,尤其是在客户端,但在使用 Mongo 时要保持一致性,那么这个库为您提供了比前面提到的 mongo-dot-notation NPM 模块更多的选择。

                  P.S 我最初只是想将此作为评论提及,但显然我的 S/O 代表不够高,无法发表评论。因此,不想强加 SzybkiSasza 的评论,只是想强调提供替代模块。

                  【讨论】:

                    【解决方案15】:

                    我尝试findAndModify() 更新预先存在的对象中的特定字段。

                    https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/

                    【讨论】:

                      【解决方案16】:

                      使用 $set 执行此过程

                      .update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
                      .exec()
                      .then(dashboardDoc => {
                          return {
                              result: dashboardDoc
                          };
                      }); 
                      

                      【讨论】:

                        【解决方案17】:

                        使用属性名称创建一个更新对象,包括必要的点路径。 (“somekey.”+ 来自 OP 的示例),然后使用它进行更新。

                        //the modification that's requested
                        updateReq = { 
                           param2 : "val2_new",
                           param3 : "val3_new"   
                        }
                        
                        //build a renamed version of the update request
                        var update = {};
                        for(var field in updateReq){
                            update["somekey."+field] = updateReq[field];
                        }
                        
                        //apply the update without modifying fields not originally in the update
                        db.collection.update({._id:...},{$set:update},{upsert:true},function(err,result){...});
                        

                        【讨论】:

                          【解决方案18】:

                          您应该考虑交替更新对象,然后简单地将对象与更新的字段一起存储。类似下面的操作

                          function update(_id) {
                              return new Promise((resolve, reject) => {
                          
                                  ObjModel.findOne({_id}).exec((err, obj) => {
                          
                                      if(err) return reject(err)
                          
                                      obj = updateObject(obj, { 
                                          some_key: {
                                              param2 : "val2_new",
                                              param3 : "val3_new"
                                          }
                                      })
                          
                                      obj.save((err, obj) => {
                                          if(err) return reject(err)
                                          resolve(obj)
                                      })
                                  })
                              })
                          }
                          
                          
                          function updateObject(obj, data) {
                          
                              let keys = Object.keys(data)
                          
                              keys.forEach(key => {
                          
                                  if(!obj[key]) obj[key] = data[key]
                          
                                  if(typeof data[key] == 'object') 
                                      obj[key] = updateObject(obj[key], data[key])
                                  else
                                      obj[key] = data[key]
                              })
                          
                              return obj
                          }
                          

                          【讨论】:

                            【解决方案19】:

                            如果你想更新一个对象的多个字段,你可以试试这个:-

                             let fieldsToUpdate = {};
                             for (const key in allFields) {
                             const fieldName = `flags.${key}`;   // define field as string literal
                                 fieldsToUpdate[fieldName] = allFields[key];
                             }
                             db.collection.update(query, { $set: { ...fieldsToUpdate } } );
                            

                            【讨论】:

                              猜你喜欢
                              • 2022-07-23
                              • 1970-01-01
                              • 2019-10-21
                              • 2019-05-14
                              • 2018-11-07
                              • 1970-01-01
                              • 2019-08-29
                              • 2020-05-20
                              • 1970-01-01
                              相关资源
                              最近更新 更多