Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MongoDB project updated record in a nested array in "findAndModify" query

Tags:

mongodb

I am trying to find a record in a nested array, modify it, and then project it.

Let's say I have the following collection:

{
    "_id" : ObjectId("56558dfc45a06f51decb4ab7"),
    "arr" : [ 
        {
            "cond" : 1.0000000000000000,
            "upd" : 2.0000000000000000
        }, 
        {
            "cond" : 2.0000000000000000,
            "upd" : 3.0000000000000000
        }, 
        {
            "cond" : 4.0000000000000000,
            "upd" : 5.0000000000000000
        }, 
        {
            "cond" : 6.0000000000000000,
            "upd" : 7.0000000000000000
        }, 
        {
            "cond" : 8.0000000000000000,
            "upd" : 9.0000000000000000
        }
    ]
}

I want to find the record where "cond=4", update it to "upd=55" and return the record.

I've tried the following query:

db.tests.findAndModify({
    query:{
        "arr": {"$elemMatch": {"cond":4}}
    },
    update: {
        "$set": {"arr.$": {"cond": 4, "upd": 55, "new": true}}
    },
    fields: {"arr.$":1}
})

It performs the update well, but I receive the following result:

{
    "_id" : ObjectId("56558dfc45a06f51decb4ab7"),
    "arr" : [ 
        {}, 
        {}, 
        {}, 
        {}, 
        {}
    ]
}

*I'm using MongoDB version :3.0.7

like image 699
skme Avatar asked Oct 31 '22 14:10

skme


1 Answers

You can also try the positional $ operator used with the findAndModify() method. The operator will identify the element in an array to update without explicitly specifying the position of the element in the array. Note, the array field must appear as part of the query document and to return the document with the modifications made on the update, use the new option, so your update would be like

db.tests.findAndModify({
    query: {
        "arr.cond": 4
    },
    update: {
        "$set": {"arr.$.upd": 55 }
    },
    new : true,
    fields: { "arr": 1, "_id": 0 }   
})

will produce the ouput:

{
    "arr": [
        {
            "cond" : 1,
            "upd" : 2
        },
        {
            "cond" : 2,
            "upd" : 3
        },
        {
            "cond" : 4,
            "upd" : 55
        },
        {
            "cond" : 6,
            "upd" : 7
        },
        {
            "cond" : 8,
            "upd" : 9
        }
    ]
}

Since you want to return the updated document, with projection the positional $ projection operator can only be used in the projection document of the find() method or the findOne() method so the findAndModify()'s field option will not project that part of the array using the $ projection operator.

A workaround would be to use the native JavaScript filter() method on the returned arr field as

var result = db.tests.findAndModify({
    query: {
        "arr.cond": 4
    },
    update: {
        "$set": {"arr.$.upd": 55 }
    },
    new : true,
    fields: { "arr": 1, "_id": 0 }   
})

var updated = []
if (result && result.arr) updated = result.arr.filter(function (item) { return item.cond == 4; });
printjson(updated);

This will print

[ { "cond" : 4, "upd" : 55 } ]

-- UPDATE --

Or the $elemMatch projection as you suggested in the comments below:

var result = db.tests.findAndModify({
    query: {
        "arr.cond": 4
    },
    update: {
        "$set": {"arr.$.upd": 55 }
    },
    new : true,
    fields: {"arr": {"$elemMatch": { "cond": 4 } }, "_id": 0 }   
})

printjson(result);

Output:

{ "arr" : [ { "cond" : 4, "upd" : 55 } ] }
like image 71
chridam Avatar answered Dec 27 '22 07:12

chridam