如何部分更新MongoDB中的对象,使新对象与现有对象重叠/合并

如何部分更新MongoDB中的对象,使新对象与现有对象重叠/合并,mongodb,mongodb-java,Mongodb,Mongodb Java,鉴于此文档保存在MongoDB中 { _id : ..., some_key: { param1 : "val1", param2 : "val2", param3 : "val3" } } 需要保存来自外部世界的具有关于param2和param3的新信息的对象 var new_info = { param2 : "val2_new", param3 : "val3_new" }; 我想在对象的现有状态上合并

鉴于此文档保存在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完全按照要求进行操作,并将某些_键设置为该值。替换旧的

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}
让MongoDB只更新新字段(而不显式地逐个声明)的方法是什么?要获得此信息:

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

我使用的是Java客户端,但是如果我正确理解了这个问题,您希望用另一个文档的内容更新一个文档,但只更新尚未出现的字段,并且完全忽略已设置的字段(即使设置为另一个值)

在一个命令中无法做到这一点

您必须首先查询文档,找出要
$set
的内容,然后更新它(使用旧值作为匹配筛选器,以确保在这两者之间不会同时获得更新)


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

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

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

Mongo允许您使用
约定更新嵌套文档。看一看:。这里还有一个过去关于合并更新的问题,就像你正在寻找的那个,我相信:

看起来你可以设置哪些可以实现你想要的。

我用自己的函数解决了这个问题。若要更新文档中的指定字段,则需要清楚地对其进行寻址

例如:

{
    _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'));

可以使用点表示法访问和设置对象内部的字段,而不影响这些对象的其他属性

给定您在上面指定的对象:

> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })
我们可以只更新
一些密钥。param2
一些密钥。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"
    }
}

你想钻研多深就钻研多深。这对于在不影响现有属性的情况下向对象添加新属性也很有用。

我用这种方法成功地做到了:

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”是特定于我的实现的,它只是要修改的键的字符串。

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

它具有
.flatte
功能,允许您将对象更改为一组平坦的属性,然后将这些属性赋予$set修饰符,而无需担心现有DB对象的任何属性将被删除/覆盖

摘自
mongo点符号
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
  }
}
*/
然后它形成完美的选择器-它将只更新给定的属性。
编辑:有时候我喜欢当考古学家;)

使用$set执行此过程

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

使用包含必要点路径的属性名称创建更新对象。(“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){...});
如果有多个字段要更新

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

是的,最好的方法是将对象表示法转换为平面键值字符串表示法,如本注释所述:

我想强调使用这个NPM库的另一种方法:它允许您使用点符号操作不同的对象

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

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点表示法
NPM模块相比,该库为您提供了更多的选项


顺便说一句,我本来只想把它作为一个评论提出来,但很明显,我的S/O代表的级别不够高,无法发表评论。因此,我不想插手SzybkiSasza的评论,只想强调提供一个替代模块。

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


启动
Mongo 4.2
,可以接受聚合管道,该管道允许使用聚合运算符,例如,从输入文档和新添加的字段中输出所有现有字段:

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.update({}, [{ $addFields: { some_key: new_info } }], { multi: true })
// { 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
      完全执行您需要的操作:更新对象,使新对象与现有对象重叠/合并:
    • 在这种情况下,
      {param2:“val2_new”,param3:“val3_new”}
      将通过保持
      param1
      不变并添加或替换
      param2
      param3
      而合并到现有的
      some_键中
  • 不要忘记
    {multi:true}
    ,否则只会更新第一个匹配的文档


您应该考虑交换地更新对象,然后简单地用更新的字段存储对象。类似下面所做的事情

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
}


希望这有帮助

你可以做一个上插,
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
}
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 
db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } ); 
db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )
let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
   update['some_key.' + param] = new_info[param]
})
db.collection.update(  { _id:...} , { $set: { ...update  } } 
db.collection.update(
   { _id:...},
   [{"$set":{
      "some_key":{
        "$mergeObjects":[
          "$some_key",
          new info or { param2 : "val2_new", param3 : "val3_new"}
       ]
      }
   }}]
)