How can I update a property of an object that is contained in an array of a parent object using mongodb?

MongodbNested

Mongodb Problem Overview


I'm using Mongo to be my database. i have a data:

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

now, i wanna to motify user's friends' emails ' email, whose _id is '123' i write like this:

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

it's easy, but , it's wrong , when the emails array has two or more data. so, my question is: how can i motify the data in a nested filed --- just have two or more nested array? Thanks.

Mongodb Solutions


Solution 1 - Mongodb

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.

Solution 2 - Mongodb

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: '[email protected]', using:'true'}, 
        {email:'[email protected]', using : 'false'}
     ]}
   ]
 }

Suppose [email protected] is being updated to [email protected]. 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 : "[email protected]"
}}, {
    "arrayFilters": [
      {"updateFriend.name" : "lucy"},
      {"updateEmail.email" : "[email protected]"}
    ]
})

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.

Solution 3 - Mongodb

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

Solution 4 - Mongodb

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 5 - Mongodb

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionallen wangView Question on Stackoverflow
Solution 1 - MongodbCristian LupascuView Answer on Stackoverflow
Solution 2 - MongodbKanembelView Answer on Stackoverflow
Solution 3 - MongodbKacperView Answer on Stackoverflow
Solution 4 - MongodbEisneimView Answer on Stackoverflow
Solution 5 - MongodbadamView Answer on Stackoverflow