How to Update Multiple Array Elements in mongodb

I have a Mongo document which holds an array of elements.

I'd like to reset the .handled attribute of all objects in the array where .profile = XX.

The document is in the following form:

{
    "_id": ObjectId("4d2d8deff4e6c1d71fc29a07"),
    "user_id": "714638ba-2e08-2168-2b99-00002f3d43c0",
    "events": [{
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 10,
            "data": "....."
        } {
            "handled": 1,
            "profile": 20,
            "data": "....."
        }
        ...
    ]
}

so, I tried the following:

.update({"events.profile":10},{$set:{"events.$.handled":0}},false,true)

However it updates only the first matched array element in each document. (That's the defined behaviour for $ - the positional operator.)

How can I update all matched array elements?


Solution 1:

UPDATE: As of Mongo version 3.6, this answer is no longer valid as the mentioned issue was fixed and there are ways to achieve this. Please check other answers.


At this moment it is not possible to use the positional operator to update all items in an array. See JIRA http://jira.mongodb.org/browse/SERVER-1243

As a work around you can:

  • Update each item individually (events.0.handled events.1.handled ...) or...
  • Read the document, do the edits manually and save it replacing the older one (check "Update if Current" if you want to ensure atomic updates)

Solution 2:

With the release of MongoDB 3.6 ( and available in the development branch from MongoDB 3.5.12 ) you can now update multiple array elements in a single request.

This uses the filtered positional $[<identifier>] update operator syntax introduced in this version:

db.collection.update(
  { "events.profile":10 },
  { "$set": { "events.$[elem].handled": 0 } },
  { "arrayFilters": [{ "elem.profile": 10 }], "multi": true }
)

The "arrayFilters" as passed to the options for .update() or even .updateOne(), .updateMany(), .findOneAndUpdate() or .bulkWrite() method specifies the conditions to match on the identifier given in the update statement. Any elements that match the condition given will be updated.

Noting that the "multi" as given in the context of the question was used in the expectation that this would "update multiple elements" but this was not and still is not the case. It's usage here applies to "multiple documents" as has always been the case or now otherwise specified as the mandatory setting of .updateMany() in modern API versions.

NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.

However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.

So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.

See also positional all $[] which also updates "multiple array elements" but without applying to specified conditions and applies to all elements in the array where that is the desired action.

Also see Updating a Nested Array with MongoDB for how these new positional operators apply to "nested" array structures, where "arrays are within other arrays".

IMPORTANT - Upgraded installations from previous versions "may" have not enabled MongoDB features, which can also cause statements to fail. You should ensure your upgrade procedure is complete with details such as index upgrades and then run

   db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

Or higher version as is applicable to your installed version. i.e "4.0" for version 4 and onwards at present. This enabled such features as the new positional update operators and others. You can also check with:

   db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

To return the current setting

Solution 3:

What worked for me was this:

db.collection.find({ _id: ObjectId('4d2d8deff4e6c1d71fc29a07') })
  .forEach(function (doc) {
    doc.events.forEach(function (event) {
      if (event.profile === 10) {
        event.handled=0;
      }
    });
    db.collection.save(doc);
  });

I think it's clearer for mongo newbies and anyone familiar with JQuery & friends.