MongoDB findAndModify()

In MongoDB the db.collection.findAndModify() method modifies and returns a single document.

The collection part is the name of the collection with which to perform the operation against.

Example

Suppose we have a collection called pets that contains the following documents:

{ "_id" : 1, "name" : "Wag", "type" : "Dog" }
{ "_id" : 2, "name" : "Bark", "type" : "Dog" }
{ "_id" : 3, "name" : "Meow", "type" : "Cat" }

We can use the db.collection.findAndModify() method to modify one of those documents.

db.pets.findAndModify({
    query: { type: "Dog" },
    update: { type: "Cow" }
})

Result:

{ "_id" : 1, "name" : "Wag", "type" : "Dog" } 

By default, it returns the original document (not the modified version).

Note that only one dog was updated, even though there are two dogs in the collection.

Let’s check the collection.

db.pets.find()

Result:

{ "_id" : 1, "type" : "Cow" }
{ "_id" : 2, "name" : "Bark", "type" : "Dog" }
{ "_id" : 3, "name" : "Meow", "type" : "Cat" }

We can see that the first document now contains a cow instead of a dog. However, we can also see that the document’s name field has disappeared.

That’s because, when you pass a document to the update argument, db.collection.findAndModify() performs a replacement.

Using an Update Operator

If we wanted to update a field, but leave the rest of the document intact, we would need to include the relevant update operator expression/s in our document.

With the collection in its current state, let’s perform another findAndModify() operation against it, but this time we’ll use the $set update operator to set just the field we want changed.

db.pets.findAndModify({
    query: { type: "Dog" },
    update: { $set: { type: "Horse" } }
})

Result:

{ "_id" : 2, "name" : "Bark", "type" : "Dog" }

This time the remaining dog was updated.

Let’s take a look at the collection.

db.pets.find()

Result:

{ "_id" : 1, "type" : "Cow" }
{ "_id" : 2, "name" : "Bark", "type" : "Horse" }
{ "_id" : 3, "name" : "Meow", "type" : "Cat" }

This time the relevant field was updated, and the rest of the document remained intact.

Return the Modified Document

By default, the original document is returned when you use db.collection.findAndModify().

If you’d prefer to have the modified document returned instead, use the new parameter.

By default, this is new: false, which results in the original document being returned. But specifying new: true results in the modified document being returned instead.

Let’s make another modification, but this time we’ll use new: true.

db.pets.findAndModify({
    query: { name: "Meow" },
    update: { $set: { name: "Scratch" } },
    new: true
})

Result:

{ "_id" : 3, "name" : "Scratch", "type" : "Cat" }

So we renamed Meow to Scratch and the output reflects our changes.

Upserts

You can perform upserts by specifying upsert: true.

An upsert is an option that you can use on update operations. If the specified document doesn’t exist, a new one is inserted. If it does exist, then the original document is updated (and no document is inserted).

Example using upsert: false

Here’s an example of trying to update a non-existent document when upsert: false.

db.pets.findAndModify({
    query: { _id: 4 },
    update: { _id: 4, name: "Fetch", type: "Dog" },
    new: true
})

Result:

null

The document didn’t exist in the collection and so findAndModify() returned null. Even though we didn’t specify upsert: false, we know it was false because that’s the default value (i.e. that’s the value that’s used when you don’t specify an upsert option).

If we take another look in the collection, we can see that the document wasn’t upserted.

db.pets.find()

Result:

{ "_id" : 1, "type" : "Cow" }
{ "_id" : 2, "name" : "Bark", "type" : "Horse" }
{ "_id" : 3, "name" : "Scratch", "type" : "Cat" }

Example using upsert: true

Now here it is again, but this time we specify upsert: true.

db.pets.findAndModify({
    query: { _id: 4 },
    update: { _id: 4, name: "Fetch", type: "Dog" },
    new: true,
    upsert: true
})

Result:

{ "_id" : 4, "name" : "Fetch", "type" : "Dog" }

This time a new document is upserted and we see the upserted document as the output (because we specified new: true).

Let’s check the collection again.

db.pets.find()

Result:

{ "_id" : 1, "type" : "Cow" }
{ "_id" : 2, "name" : "Bark", "type" : "Horse" }
{ "_id" : 3, "name" : "Scratch", "type" : "Cat" }
{ "_id" : 4, "name" : "Fetch", "type" : "Dog" }

So we can see that the new document was in fact upserted.

The arrayFilters Parameter

When working with arrays, you can use the arrayFilters parameter along with the positional $ operator to determine which array elements to update. This enables you to update an array element based on its value, even if you don’t know its position.

For example, suppose we have a collection called players with the following documents:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 17, 18 ] }
{ "_id" : 3, "scores" : [ 15, 11, 8 ] }

We could run the following query to update only those array elements that have a value higher than a certain amount (in this case 10).

db.players.findAndModify({
   query: { scores: { $gte: 10 } },
   update: { $set: { "scores.$[e]" : 10 } },
   arrayFilters: [ { "e": { $gte: 10 } } ]
})

Result:

{ "_id" : 2, "scores" : [ 8, 17, 18 ] }

As expected, this only updates one document, even though two documents match the criteria.

Here’s what the documents look like now.

db.players.find()

Result:

{ "_id" : 1, "scores" : [ 1, 5, 3 ] }
{ "_id" : 2, "scores" : [ 8, 10, 10 ] }
{ "_id" : 3, "scores" : [ 15, 11, 8 ] }

Document 2 had two array elements updated, because those elements matched the criteria.

More Information

The db.collection.findAndModify() method also accepts other parameters, such as writeConcern, collation, bypassDocumentValidation and more.

See the MongoDB documentation for db.collections.findAndModify() for more information.