1

I have docs with the following fields in MongoDB collection:

var doc = {
container_id:12,
steps: [
    {
        name:'name1',
    },
    {
        name:'name2',
    }
]

}

I need to find all docs which have container_id == 12 and where last 'steps' element matches 'name2' and update it with a new value.

I have tried the following:

let findQ = {'container_id': 12, "steps:{$slice:-1}.name": "name2"};
let updateQ = {"$set": {'steps.$.name': "new name3"}};

db.collection(myCollection).update(
  findQ,
  updateQ,
  {multi: true},
  function (err, doc) { ... }
);
Jan B.
  • 6,030
  • 5
  • 32
  • 53
Serhiy
  • 1,893
  • 9
  • 30
  • 48

2 Answers2

0

You need to add a $where condition to the query portion of the statment in order to make sure that the document contains the array match in the "last" position. But that's only an "additional" constraint, and the "position" should be obtained from the standard query option of "steps.name": "name2":

So given sample documents:

/* 1 */
{
    "_id" : ObjectId("59c39cf859f55d64d6e30293"),
    "container_id" : 12.0,
    "steps" : [ 
        {
            "name" : "name1"
        }, 
        {
            "name" : "name2"
        }
    ]
}

/* 2 */
{
    "_id" : ObjectId("59c39d1b59f55d64d6e30294"),
    "container_id" : 12.0,
    "steps" : [ 
        {
            "name" : "name1"
        }, 
        {
            "name" : "name2"
        }, 
        {
            "name" : "name3"
        }
    ]
}

Issuing the query:

db.collection('junk').update(
  { 
    "container_id": 12,
    "steps.name": "name2",
    "$where": "this.steps.slice(-1)[0].name === 'name2'"
  },
  { "$set": { "steps.$.name": "new name4" } },
  { "multi": true }
)

Results in:

/* 1 */
{
    "_id" : ObjectId("59c39cf859f55d64d6e30293"),
    "container_id" : 12.0,
    "steps" : [ 
        {
            "name" : "name1"
        }, 
        {
            "name" : "new name4"
        }
    ]
}

/* 2 */
{
    "_id" : ObjectId("59c39d1b59f55d64d6e30294"),
    "container_id" : 12.0,
    "steps" : [ 
        {
            "name" : "name1"
        }, 
        {
            "name" : "name2"
        }, 
        {
            "name" : "name3"
        }
    ]
}

So the second document though containing an array element with the matching value of "name2", gets excluded by the $where clause since it's not the last element in the array.

Only the first document gets updated because it matched both of the conditions.

NOTE: You still need the "steps.name": "name2" in the query portion of .update() in order to give the position for the positional $ operator. If there are actually more than one match in the array for the same value, then it's not possible to do at this time. See How to Update Multiple Array Elements in MongoDB for more detail on multiple matches.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
0

According to description as mentioned into above question please try executing following update operation into MongoDB shell.

db.myCollection.update({
        "container_id": 12.0,
        steps: {
            $elemMatch: {
                "name": "name2"
            }
        }
    }, {
        $set: {
            'steps.$.name': 'name3'
        }
    }, {
        multi: true
    }


)
Rubin Porwal
  • 3,736
  • 1
  • 23
  • 26
  • Please read [Specify Multiple Conditions for Array Elements](https://docs.mongodb.com/manual/tutorial/query-arrays/#specify-multiple-conditions-for-array-elements), then intended usage of `$elemMatch` is for "multiple conditions". This is **one** condition, therefore that part of the OP's question is correct. `$elemMatch` is not a magic bullet. The real point of the question is "matching the last array element: – Neil Lunn Sep 21 '17 at 21:35