鉴于此文档保存在 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 客户端,但任何示例都将不胜感激
我用自己的功能解决了它。如果您想更新文档中的指定字段,您需要清楚地解决它。
例子:
{
_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'));
如果我正确理解了这个问题,您想用另一个文档的内容更新一个文档,但只更新尚未存在的字段,并完全忽略已经设置的字段(即使是另一个值)。
没有办法在单个命令中做到这一点。
您必须先查询文档,找出您想要 $set
的内容,然后对其进行更新(使用旧值作为匹配过滤器,以确保您不会在两者之间获得并发更新)。
您的问题的另一种解读是您对 $set
感到满意,但不想明确设置所有字段。那么你将如何传递数据呢?
您知道您可以执行以下操作:
db.collection.update( { _id:...} , { $set: someObjectWithNewData }
db.collection.update( { _id:...} , { $set: someObjectWithNewData, { $multi: true } } )
您可以使用点符号来访问和设置对象深处的字段,而不会影响这些对象的其他属性。
鉴于您在上面指定的对象:
> db.test.insert({"id": "test_object", "some_key": {"param1": "val1", "param2": "val2", "param3": "val3"}})
WriteResult({ "nInserted" : 1 })
我们可以只更新 some_key.param2
和 some_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"
}
}
您可以随心所欲地深入研究。这对于在不影响现有属性的情况下向对象添加新属性也很有用。
最好的解决方案是从对象中提取属性并使其成为平面点符号键值对。例如,您可以使用这个库:
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
}
}
*/
然后它形成完美的选择器——它只会更新给定的属性。编辑:有时我喜欢成为考古学家;)
从 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 不变,添加或替换 param2 和 param3。
请注意表示使用聚合管道的方括号。
由于这是一个聚合管道,我们可以使用 $addFields。
$addFields 完全符合您的需要:更新对象,以便新对象与现有对象重叠/合并:
在这种情况下,{ param2: "val2_new", param3: "val3_new" } 将通过保持 param1 不变并添加或替换 param2 和 param3 来合并到现有的 some_key 中。
Mongo 允许您使用 .
约定更新嵌套文档。看一看:Updating nested documents in mongodb。这是过去有关合并更新的另一个问题,我相信您正在寻找的问题:MongoDB atomic update via 'merge' document
我以这种方式成功地做到了:
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' 特定于我的实现,它只是您要修改的键的字符串。
你可以做一个 upsert,MongoDB 中的这个操作用于将文档保存到集合中。如果文档匹配查询条件,那么它将执行更新操作,否则它将插入一个新文档到集合中。
类似于下面的东西
db.employees.update(
{type:"FT"},
{$set:{salary:200000}},
{upsert : true}
)
您可以在基于聚合的更新中使用 $mergeObjects
。就像是
db.collection.update(
{ _id:...},
[{"$set":{
"some_key":{
"$mergeObjects":[
"$some_key",
new info or { param2 : "val2_new", param3 : "val3_new"}
]
}
}}]
)
更多示例here
// 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);
db.collection.update( { _id:...} , { $set: { some_key : new_info } }
至
db.collection.update( { _id: ..} , { $set: { some_key: { param1: newValue} } } );
希望这有帮助!
您必须使用嵌入式文档(对路径对象进行字符串化)
let update = {}
Object.getOwnPropertyNames(new_info).forEach(param => {
update['some_key.' + param] = new_info[param]
})
因此,在 JavaScript 中,您可以使用扩展运算符 (...) 来更新
db.collection.update( { _id:...} , { $set: { ...update } }
是的,最好的方法是将对象表示法转换为平面键值字符串表示,如此评论中所述: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-notation
NPM 模块更多的选项。
PS我最初只是想将此作为评论提及,但显然我的 S/O 代表还不够高,无法发表评论。因此,不想强加 SzybkiSasza 的评论,只是想强调提供替代模块。
我尝试使用 findAndModify()
更新预先存在的对象中的特定字段。
https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/
使用 $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){...});
您应该考虑交替更新对象,然后简单地将对象与更新的字段一起存储。像下面做的事情
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
}
如果你想更新一个对象的多个字段,你可以试试这个:-
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 } } );
collection.updateOne
,因为不推荐使用 collection.update
。
db.users.update( { _id: ObjectId("594dbc3186bb5c84442949b1") }, { $set: { resume: { url: "http://i.imgur.com/UFX0yXs.jpg" } } } )