238

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

4

19 回答 19

128

我用自己的功能解决了它。如果您想更新文档中的指定字段,您需要清楚地解决它。

例子:

{
    _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'));
于 2013-04-20T10:14:32.387 回答
119

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

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

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


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

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

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 
于 2012-04-24T01:37:18.550 回答
27

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

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

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

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

于 2015-11-14T17:33:13.447 回答
24

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

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

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

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

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

于 2016-09-06T20:43:02.490 回答
12

Mongo 允许您使用.约定更新嵌套文档。看一看:更新 mongodb 中的嵌套文档。这是过去关于合并更新的另一个问题,就像我相信的那样:MongoDB atomic update via 'merge' document

于 2012-04-24T01:40:12.720 回答
11

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
于 2019-06-14T19:35:43.373 回答
8

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

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

于 2016-07-24T16:48:52.507 回答
5

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

类似于下面的东西

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )
于 2019-12-09T21:58:47.110 回答
3
    // 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);
于 2018-07-28T16:42:38.240 回答
3

您可以$mergeObjects在基于聚合的更新中使用。就像是

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

更多示例here

于 2020-11-25T15:03:08.370 回答
2

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

于 2012-04-24T01:41:52.503 回答
2
db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 

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

希望这有帮助!

于 2019-11-23T03:44:10.537 回答
2

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

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

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

db.collection.update(  { _id:...} , { $set: { ...update  } } 
于 2020-01-31T03:11:35.467 回答
1

是的,最好的方法是将对象表示法转换为平面键值字符串表示,如此评论中所述: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 的评论,只是想强调提供替代模块。

于 2018-08-01T19:37:01.803 回答
1

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

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

于 2019-01-14T23:06:32.680 回答
0

使用 $set 做这个过程

.update({"_id": args.dashboardId, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
.exec()
.then(dashboardDoc => {
    return {
        result: dashboardDoc
    };
}); 
于 2017-02-03T12:19:15.030 回答
0

使用属性名称创建一个更新对象,包括必要的点路径。(“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){...});
于 2018-06-08T23:23:17.757 回答
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
}
于 2019-09-15T12:46:57.440 回答
0

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

 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 } } );
于 2021-11-23T15:08:57.087 回答