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

Era*_*dan 157 mongodb mongodb-java

鉴于此文档保存在MongoDB中

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
   }
}
Run Code Online (Sandbox Code Playgroud)

对新信息的对象param2,并param3从外面的世界需要保存

var new_info = {
    param2 : "val2_new",
    param3 : "val3_new"
};
Run Code Online (Sandbox Code Playgroud)

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

这样做

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } } 
Run Code Online (Sandbox Code Playgroud)

将导致MongoDB完全按照要求执行,并将some_key设置为该值.取代旧的.

{
   _id : ...,
   some_key: { 
      param2 : "val2_new",
      param3 : "val3_new"
   }
}
Run Code Online (Sandbox Code Playgroud)

MongoDB只更新新字段的方法是什么(没有明确说明它们)?得到这个:

{
   _id : ...,
   some_key: { 
        param1 : "val1",
        param2 : "val2_new",
        param3 : "val3_new"
   }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Java客户端,但任何例子都将受到赞赏

Thi*_*ilo 95

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

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

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


您对问题的另一个解读是您很满意$set,但不想明确设置所有字段.你会如何传递数据呢?

你知道你可以做到以下几点:

db.collection.update(  { _id:...} , { $set: someObjectWithNewData } 
Run Code Online (Sandbox Code Playgroud)

  • 没有$ multi参数.有一个多选项(这是你链接到的),但这不会影响字段的更新方式.它控制您更新单个文档还是更新与查询参数匹配的所有文档. (9认同)
  • 这没有任何意义。在处理 mongo 时,更新也最好具有原子操作。每当其他人更改您的数据时,首先读取它会导致脏读。由于 mongo 没有事务,它会很乐意为你破坏数据。 (2认同)

小智 95

我用自己的功能解决了这个问题.如果要更新文档中的指定字段,则需要清楚地解决.

例:

{
    _id : ...,
    some_key: { 
        param1 : "val1",
        param2 : "val2",
        param3 : "val3"
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你只想更新param2,那就错了:

db.collection.update(  { _id:...} , { $set: { some_key : new_info  } }  //WRONG
Run Code Online (Sandbox Code Playgroud)

你必须使用:

db.collection.update(  { _id:...} , { $set: { some_key.param2 : new_info  } } 
Run Code Online (Sandbox Code Playgroud)

所以我写了一个像这样的函数:

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'));
Run Code Online (Sandbox Code Playgroud)

  • 这应该是公认的答案.有关更好的flattenObject函数,请参阅https://gist.github.com/penguinboy/762197 (11认同)

Sam*_*war 17

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

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

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

我们可以更新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"
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以深入钻研.这对于向对象添加新属性而不影响现有属性也很有用.


Szy*_*sza 12

最好的解决方案是从对象中提取属性并使它们成为平点符号键值对.你可以使用这个库:

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
  }
}
*/
Run Code Online (Sandbox Code Playgroud)

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


Pav*_*ler 7

Mongo允许您使用.约定更新嵌套文档.看一下:在mongodb中更新嵌套文档.这里是关于合并更新的过去的另一个问题,比如你正在寻找的那个我相信:通过'merge'文件进行MongoDB原子更新

  • 这很好知道,但不是我的问题:) (3认同)

Xav*_*hot 7

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" } }
Run Code Online (Sandbox Code Playgroud)
  • 第一部分{}是匹配查询,过滤要更新的文档(在本例中为所有文档)。

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

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


use*_*814 6

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

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

更多示例here


小智 5

这样我就成功了:

db.collection.update(  { _id:...} , { $set: { 'key.another_key' : new_info  } } );
Run Code Online (Sandbox Code Playgroud)

我有一个可以动态处理我的个人资料更新的功能

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

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


小智 5

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

类似于下面的东西

db.employees.update(
    {type:"FT"},
    {$set:{salary:200000}},
    {upsert : true}
 )
Run Code Online (Sandbox Code Playgroud)