如何使用 mongodb 更新包含在父对象数组中的对象的属性?

我使用 MongoDB 作为我的数据库。我有一个数据:

 {
_id : '123'
friends: [
{name: 'allen', emails: [{email: '11111', using: 'true'}]}
]
}

现在,我想修改用户的朋友的电子邮件’电子邮件,其 _ id 是’123’ 我是这样写的:

db.users.update ({_id: '123'}, {$set: {"friends.0.emails.$.email" : '2222'} })

这很容易,但是,当电子邮件数组有两个或更多的数据时,这是错误的。 所以,我的问题是: 如何修改嵌套文件中的数据——-只有两个或多个嵌套数组? 谢谢。

115150 次浏览

You need to use the Dot Notation for the arrays.

That is, you should replace the $ with the zero-based index of the element you're trying to update.

For example:

db.users.update ({_id: '123'}, { '$set': {"friends.0.emails.0.email" : '2222'} });

will update the first email of the first friend, and

db.users.update ({_id: '123'}, { '$set': {"friends.0.emails.1.email" : '2222'} })

will update the second email of the first friend.

update something in a multi level arry is really pain in the ass, my way of doing it:replace the deep level arry.

db.user.findOne({_id:'123'},{friends:1}).lean().exec(function(err,user){
var whichArrayToUpdate;
for (var ii = 0; ii < user.friends.length; ii++) {
for (var jj = 0; i < user.friends[ii].emails; jj++) {
if(user.friends[ii].emails[jj].email == '1111' ){// update it below


user.friends[ii].emails[jj].email == 'what ever you want to set to.';


whichArrayToReplace = user.friends[ii].emails;
break;
}
};
};


db.user.update({'friends.name':'allen'},{$set{'friends.$.email': whichArrayToReplace} })
})

but, why not use the save() method? the save() will replace your whole document, if your document is small that's ok, but if your document is relly big , it's a better idea to replace just part of your document.

or do the loop, use the position of the top level array and second level array(ii and jj) to update.

my advice is: when you design schema, don't put array in another array unless you won't do any update for that array.

Solution using Mongoose:

    Users.findById("123", function(err, user) {


var friends = user.friends;
for ( i=0; i < friends.length; i++ ) {
if (friends[i].name == 'allen') {
friends[i].email = '2222';


user.save(function(err) {
if (err) throw err;
console.log("email updated");
});
} else {
console.log("no friends named allen");
}
}


}

I have a similar situation where I have main category such as:

{
"service": {
"isReviewed": false,
"_id": "5ea36b27d7ae560845afb88d",
"mainCategory": "message ",
"SubCategory": [
{
"priceChanged": true,
"_id": "5ea36b27d7ae560845afb88e",
"subCatPrice": "26",
"subCatName": "mustach trimming"
}
],
"shopName": "paddy the barber",
"user": "5ea1fd5b69512dc72ad2f48c",
"date_created": "2020-04-24T22:41:43.368Z",
"__v": 5
}
}

Now, it took a while figure out how to update one piece of the subdocument. as the sub doc is store as an object.

So All id, is first I check the _id of the doc when its passed through the put/:id. Then, I checked the owner of doc against the req.user.

if everything okay, I looped through the array then I produced a new object. Then, all I did, is I check each req.body for price or name, if there is a new price, I would just update the price value in the object. if there is new value then the object remains the same.

then all I said, service.SubCategory = newObject and store the data.

Its working fine, however, I ran into the problem of subdoc _id getting updated, so I fixed that by keeping the old value as its.

Now, in terms of performance and optimization, I am not entirely sure if this is the correct way but its working and if they're better away I am willing to change.

Here is the code:

const newObject = {
subCatPrice: "",
subCatName: "",
_id: "",
priceChanged: false
};


let oldDetails = service.SubCategory;
for (const old of oldDetails) {
newObject.subCatPrice = old.subCatPrice;
newObject.subCatName = old.subCatName;
newObject._id = old._id;
}


price ? (newObject.subCatPrice = price ) && (newObject.priceChanged = true)  : newObject.subCatPrice;
name ? (newObject.subCatName = name) : newObject.subCatName;


(service.SubCategory = newObject), await service.save();

I used the react idea of the state to be honest, where I just get a copy of the object and keep it and apply an update to the piece that I want to update.

Again in terms of clean code and all of that stuff, I am not sure if this is the correct way to do it, but I am also a student of programming and I would like to learn too.

Hope this help someone

One flexible way to do updates to a multilevel array is to use arrayFilters which allows indexes to be queried for and assigned to an identifier.

db.collection.update(
{ <query selector> },
{ <update operator>: { "array.$[<identifier>].field" : value } },
{ arrayFilters: [ { <identifier>: <condition> } } ] }
)

Here's the example you provided plus a new friend with two emails:

{
_id : '123'
friends: [
{name: 'allen', emails: [
{email: '11111', using: 'true'}
]},
{name: 'lucy' , emails: [
{email: 'lucy@work.com', using:'true'},
{email:'lucyGucy@zmail.com', using : 'false'}
]}
]
}

Suppose lucyGucy@zmail.com is being updated to lucy.is.gucy@zmail.com. We can use the name and email fields in an array filter to identify the index we want to update.

db.users.update({_id:123}, {$set:{
"friends.$[updateFriend].emails.$[updateEmail].email : "lucy.is.gucy@zmail.com"
}}, {
"arrayFilters": [
{"updateFriend.name" : "lucy"},
{"updateEmail.email" : "lucyGucy@zmail.com"}
]
})

In this way the update is not sensitive to a particular array order. The example above uses two identifiers updateFriend and updateEmail which will match – for the array they are applied too – any elements fulfilling the array filter criteria. As noted in the documentation:

The <identifier> must begin with a lowercase letter and contain only alphanumeric characters.

Also while emails are likely unique I would recommend including a unique id on all subdocuments so the arrayFilters can be exact.