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


196

鉴于此文档保存在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客户端,但是任何示例都将不胜感激


2
请对$ merge问题进行投票:jira.mongodb.org/browse/SERVER-21094
Ali

Answers:


108

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

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

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


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

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

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

如果要无条件设置所有字段,则可以使用$ multi参数,如下所示: db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
Villager

13
没有$ multi参数。有一个多重选项(您链接到该选项),但这不影响字段的更新方式。它控制您是更新单个文档还是更新与查询参数匹配的所有文档。
鲍勃·科恩斯

1
这毫无意义。当与mongo打交道时,更新也最好具有原子操作。首次读取它会在其他人更改您的数据时导致读取错误。而且由于mongo没有事务,它将为您高兴地破坏数据。
froginvasion

@froginvasion:您可以通过使用旧值作为更新的匹配筛选器使其原子化,这样,如果在此期间发生并行并发更新,则更新将失败。然后,您可以检测到并采取相应措施。这称为乐观锁定。但是,是的,这取决于您的应用程序是否正确实现,Mongo本身没有内置事务。
Thilo

我认为这是在javascript中合并两个对象的更普遍的问题。IIRC的想法是简单地将新属性分配给旧属性
information_interchange

115

我用自己的功能解决了它。如果要更新文档中的指定字段,则需要明确解决。

例:

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

12
这应该是公认的答案。有关更好的flattenObject函数,请参见 gist.github.com/penguinboy/762197
steph643

谢谢您的回复帮助指导了我。我能够按如下所示修改用户集合中某个用户特定键的值:db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )
Luke Schoen

此操作是原子操作且孤立的吗?意思是,如果2个并行线程试图设置同一文档的2个不同字段,将导致竞争状态和不可预测的结果
so-random-dude

21

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

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

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

我们可以只更新some_key.param2some_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”,}
Urvashi Soni

18

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

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

它具有.flatten允许您将对象更改为平面的一组属性,然后可以将这些属性提供给$ set修饰符的功能,而无需担心现有DB对象的任何属性都将被删除/覆盖而无需使用。

来自mongo-dot-notation文档:

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
  }
}
*/

然后,它形成完美的选择器-仅更新给定的属性。编辑:我有时想当考古学家;)



6

这样我就成功了:

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

注意:“配置文件”特定于我的实现,只是您要修改的密钥的字符串。


3

您可能宁愿做一个upsert,MongoDB中的此操作用于将文档保存到集合中。如果文档符合查询条件,则它将执行更新操作,否则它将新文档插入集合中。

如下所示

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

2

开始Mongo 4.2db.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.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
    • $addFields 完全执行您所需的操作:更新对象,以便新对象可以与现有对象重叠/合并:
    • 在这种情况下,{ param2: "val2_new", param3: "val3_new" }some_key保持param1不变并合并到现有文件中,并添加或替换param2param3
  • 不要忘记{ multi: true },否则只会更新第一个匹配的文档。



1
    // 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);

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

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

希望有帮助!


1

您必须使用Embedded Documents(stringfy路径对象)

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

因此,在JavaScript中,您可以使用Spread Operator(...)进行更新

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

0

使用$ set执行此过程

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

0

使用属性名称(包括必要的点路径)创建一个更新对象。(例如,OP中的“ somekey。” +),然后使用该按钮进行更新。

//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){...});

0

是的,最好的方法是将对象表示法转换为平面键值字符串表示形式,如本注释所述: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-notationNPM模块更多的选择。

PS我本来只是想将其作为评论提及,但显然我的S / O代表不够高,无法发表评论。因此,不打算强求SzybkiSasza的评论,只是想强调提供一个替代模块。



0

您应该考虑互换性地更新对象,然后只需将对象与已更新的字段一起存储。像下面做的事情

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.