我如何在MongoDB中部分更新一个对象,以便新对象将覆盖/合并现有的一个

给定此文档保存在MongoDB中

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

需要保存一个具有来自外部的关于param2param3的新信息的对象

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客户端,但是任何示例都将受到欢迎

284174 次浏览

如果我理解正确的话,您希望用另一个文档的内容更新一个文档,但只更新尚未出现的字段,并完全忽略已经设置的字段(即使是另一个值)。

在单个命令中不可能做到这一点。

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


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

你知道你可以做以下事情:

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

Mongo允许你使用.约定更新嵌套文档。来看看:更新mongodb中的嵌套文档。这是另一个来自过去的关于合并更新的问题,就像你正在寻找的那个:MongoDB原子更新通过'文档

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

我用自己的函数解出来了。如果你想更新文档中的指定字段,你需要清楚地解决它。

例子:

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

我们可以只更新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"
}
}

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

我是这样成功做到的:

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

最好的解决方案是从对象中提取属性,并使它们成为平面点表示法键值对。例如,你可以使用这个库:

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

它有.flatten函数,允许您将对象更改为一个平坦的属性集,然后可以给$set修饰符,而不用担心您现有DB对象的任何属性将被删除/覆盖而不需要。

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

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

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

我想强调使用NPM库的另一种方法:https://www.npmjs.com/package/dot-object,它允许你使用点表示法操作不同的对象。

当接受key-value作为函数变量时,我使用这个模式以编程方式创建一个嵌套的对象属性,如下所示:

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模块提供了更多的选项。

附:我最初只是想把这作为一个评论,但显然我的S/O代表不够高,不能发表评论。所以,我并不是想强行干涉SzybkiSasza的评论,只是想强调提供一个替代模块。

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

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

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不变,并添加或替换param2param3

您应该考虑交换地更新对象,然后简单地将对象与更新后的字段存储在一起。如下所示

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

希望这对你有所帮助!

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

类似如下所示

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

你必须使用Embedded Documents (stringfy path对象)

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

因此,在JavaScript中,您可以使用扩展操作符(…)进行更新

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

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

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

更多的例子here

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

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