6

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 } ] });
Mayur Kataria
  • 675
  • 1
  • 10
  • 14

4 Answers4

10

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}}})
Ross
  • 17,861
  • 2
  • 55
  • 73
  • Thanks for the first question answer, i already tried with @drmirror answer. $pull will set null in case of subdocuments, then i have to execute 2 update statement one for updating it to null, another for removing null from subdocument. Is it possible for one update statement ? – Mayur Kataria Aug 22 '13 at 08:07
  • In 2.4 its pulling the sub documents and not setting as null. – Ross Aug 22 '13 at 08:09
  • 1
    Thanks again :) , sorry for not up voting, i am slightly new for the stackoverflow – Mayur Kataria Aug 22 '13 at 08:20
  • Use a do while might be simpler – Lpc_dark Nov 18 '13 at 22:33
2

You need to use $elemMatch to match inside an array. The query rules in MongoDB specify that for conditions on array elements, any array element where any of the conditions matches is considered a match. With $elemMatch, all conditions need to match on the same element.

drmirror
  • 3,698
  • 26
  • 26
  • Can you please write down the shell statement for the same ? – Mayur Kataria Aug 21 '13 at 11:29
  • thanks .. i tried with $elemMatch and it works. But for the 2nd problem. i don't want to execute 2 update statement just to remove the one subdocument.Is there a way ? by one statement execution – Mayur Kataria Aug 22 '13 at 08:04
0

Dot notation matches documents that its criteria can be met in any of the array elements.

If you want to match only documents that match all of your criteria in one array element (at least) then as drmirror already answered, you need to use the operator $elemMatch, see also: $elemMatch Operator

In your case that would be:

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

For the second part of your question, how to remove array elements, see the following post.

In mongoDb, how do you remove an array element by its index

In short, it's a two step operation that first sets to null the element (as you already do) and then it $pull(s) it off.

Community
  • 1
  • 1
Ioannis Lalopoulos
  • 1,491
  • 1
  • 12
  • 20
0

For Problem #1 refer to: https://stackoverflow.com/a/20601288/773953

Essentially you'll have to build the array of what you'd like to set manually.

Community
  • 1
  • 1
Zaheer
  • 2,794
  • 5
  • 28
  • 33