Search code examples
mongodbmongodb-querymongodb-update

Update/remove subdocument in MongoDB collection


Note:Please use mongodb shell to execute the codes.

Let's say i have one student document as below

{
    "_id" : 4,
"grades" : [
    {
        "grade" : 80,
        "mean" : 75,
        "std" : 8
    },
    {
        "grade" : 85,
        "mean" : 90,
        "std" : 5
    },
    {
        "grade" : 85,
        "mean" : 90,
        "std" : 5
    },
    {
        "grade" : 85,
        "mean" : 95,
        "std" : 6
    },
    {
        "grade" : 90,
        "mean" : 85,
        "std" : 5
    }
]
}

We have 2 problems :

Problem 1 : lets say you wants to updates all subdocuments with _id=4 && grades.grade = 85 && grades.std = 5, with std=6 you will write as follows

db.students.update( {'$and':[ { _id: 4},{ "grades.grade": 85 }, {"grades.std": 5 } ]}, { $set: { "grades.$.std" : 6 } } );

Now if you execute above statement multiple times(3 times) then ideally it should update 2nd,3rd sub-documents

But last sub-document is also getting updated beacause it has std=5 match, but we have given condition as $and not $or, then how come last document is getting updated?

Problem 2 : Now let's say you wants to remove the subdocument itself matching with query criteria. I tried following statement

db.students.update({_id:4,'grades.grade':85},{'$unset':{'grades.$':1}})

because $unset will put null in case of sub-documents/arrays, how to solve this problem?

How to replicate in your mongodb console ?

db.students.insert( { "_id" : 4, "grades" : [ { grade: 80, mean: 75, std: 8 }, { grade: 85, mean: 90, std: 5 }, { grade: 85, mean: 90, std: 5 }, { grade: 85, mean: 95, std: 6 }, { grade: 90, mean: 85, std: 5 } ] });

Solution

  • The '$' operator only updates the first match so given:

    db.students.insert({ "_id" : 4, "grades" : [ 
        { grade: 80, mean: 75, std: 8 }, 
        { grade: 85, mean: 90, std: 5 }, 
        { grade: 85, mean: 90, std: 5 }, 
        { grade: 85, mean: 95, std: 6 }, 
        { grade: 90, mean: 85, std: 5 } ]});
    

    To update you need to target with $elemMatch like so:

    db.students.update( { _id: 4, "grades": {$elemMatch: {"grade": 85, "std": 5 }}}, 
                        { $set: { "grades.$.std" : 6 } });
    

    However, you have two grades that match {"grades.grade": 85, "grades.std": 5} and $ only updates the first so you need to loop until all updated:

    db.students.update( { _id: 4, "grades": {$elemMatch: {"grade": 85, "std": 5 }}}, 
                        { $set: { "grades.$.std" : 6 } })
    while (db.getLastErrorObj()['n'] > 0) {
        db.students.update( { _id: 4, "grades": {$elemMatch: {"grade": 85, "std": 5 }}}, 
                            { $set: { "grades.$.std" : 6 } })
    }
    

    Problem 2: Same thing applies - you need to loop to $pull matching elements:

    db.students.update({_id:4,'grades': {$elemMatch: {'grade':85}}}, {'$pull': {'grades': {'grade':85}}})