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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10290621/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 12:37:33  来源:igfitidea点击:

How do I partially update an object in MongoDB so the new object will overlay / merge with the existing one

mongodbmongodb-java

提问by Eran Medan

Given this document saved in MongoDB

鉴于此文档保存在 MongoDB 中

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

An object with new information on param2and param3from the outside world needs to be saved

需要保存具有来自外部世界param2param3来自外部世界的新信息的对象

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

I want to merge / overlay the new fields over the existing state of the object so that param1 doesn't get removed

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

Doing this

这样做

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

Will lead to MongoDB is doing exactly as it was asked, and sets some_key to that value. replacing the old one.

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

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

What is the way to have MongoDB update only new fields (without stating them one by one explicitly)? to get this:

有什么方法可以让 MongoDB 只更新新字段(不一一明确说明)?得到这个:

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

I'm using the Java client, but any example will be appreciated

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

采纳答案by Thilo

If I understand the question correctly, you want to update a document with the contents of another document, but only the fields that are not already present, and completely ignore the fields that are already set (even if to another value).

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

There is no way to do that in a single command.

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

You have to query the document first, figure out what you want to $setand then update it (using the old values as a matching filter to make sure you don't get concurrent updates in between).

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



Another reading of your question would be that you are happy with $set, but do not want to explicitly set all fields. How would you pass in the data then?

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

You know you can do the following:

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

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

回答by mehmatrix

I solved it with my own function. If you want to update specified field in document you need to address it clearly.

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

Example:

例子:

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

If you want to update param2 only, it's wrong to do:

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

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

You must use:

您必须使用:

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


So i wrote a function something like that:

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

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

回答by Samir Talwar

You can use dot-notation to access and set fields deep inside objects, without affecting the other properties of those objects.

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

Given the object you specified above:

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

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

We can update just some_key.param2and some_key.param3:

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

You can delve as deep as you like. This is also useful for adding new properties to an object without affecting the existing ones.

您可以根据自己的喜好进行深入研究。这对于向对象添加新属性而不影响现有属性也很有用。

回答by SzybkiSasza

The best solution is to extract properties from object and make them flat dot-notation key-value pairs. You could use for example this library:

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

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

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

It has .flattenfunction that allows you to change object into flat set of properties that could be then given to $set modifier, without worries that any property of your existing DB object will be deleted/overwritten without need.

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

Taken from mongo-dot-notationdocs:

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

And then it forms perfect selector - it will update ONLY given properties. EDIT: I like to be archeologist some times ;)

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

回答by Pavel Veller

Mongo lets you update nested documents using a .convention. Take a look: Updating nested documents in mongodb. Here's another question from the past about a merge update, like the one you're looking for I believe: MongoDB atomic update via 'merge' document

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

回答by Matt Smith

I had success doing it this way:

我这样做是成功的:

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

I have a function that handles my profileupdates dynamically

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

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

Note: 'profile' is specific to my implementation, it is just the string of the key that you would like to modify.

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

回答by Patrick Tescher

It looks like you can set isPartialObjectwhich might accomplish what you want.

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

回答by Vino

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

If you have multiple fields to be updated

如果您有多个字段要更新

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

回答by Xavier Guihot

Starting Mongo 4.2, db.collection.update()can accept an aggregation pipeline, which allows using aggregation operators such as $addFields, which outputs all existing fields from the input documents and newly added fields:

开始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" } }
  • The first part {}is the match query, filtering which documents to update (in this case all documents).

  • The second part [{ $addFields: { some_key: new_info } }]is the update aggregation pipeline:

    • Note the squared brackets signifying the use of an aggregation pipeline.
    • Since this is an aggregation pipeline, we can use $addFields.
    • $addFieldsperforms exactly what you need: updating the object so that the new object will overlay / merge with the existing one:
    • In this case, { param2: "val2_new", param3: "val3_new" }will be merged into the existing some_keyby keeping param1untouched and either add or replace both param2and param3.
  • Don't forget { multi: true }, otherwise only the first matching document will be updated.

  • 第一部分{}是匹配查询,过滤要更新的文档(在本例中为所有文档)。

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

    • 请注意方括号表示使用聚合管道。
    • 由于这是一个聚合管道,我们可以使用$addFields.
    • $addFields完全按照您的需要执行:更新对象,以便新对象与现有对象重叠/合并:
    • 在这种情况下,{ param2: "val2_new", param3: "val3_new" }some_key通过保持param1不变并添加或替换param2和来合并到现有中param3
  • 不要忘记{ multi: true },否则只会更新第一个匹配的文档。

回答by nguyenthang338

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

to

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

Hope this help!

希望这有帮助!