Pregunta

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 } ] });
¿Fue útil?

Solución

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

Otros consejos

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.

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.

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top