MongoDB Upsert Explained

Many update operations in MongoDB have the potential to be upserts. An upsert is a combination of an insert and an update.

It works like this: You perform an update operation based on filter criteria, and if there are any matches, only the matched documents are updated, but if there are no matches, then a new document is inserted.

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 could perform the following update operation that sets the upsert parameter to true:

db.pets.updateOne( 
    { name: "Wag" },
    { $set: { type: "Cow" } },
    { upsert: true }
    )

Result:

{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 } 

In this case, there was a matching document (i.e. there’s a document with name: "Wag") and therefore the matching document was updated. Nothing was inserted.

We can verify this as follows:

db.pets.find()

Result:

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

The first document now has a type of Cow.

Lets’ run another update operation, again using upsert: true. But this time, there will be no matching document to update.

db.pets.updateOne( 
    { name: "Bubbles" },
    { $set: { type: "Fish" } },
    { upsert: true }
    )

Result:

 {
 "acknowledged" : true,
 "matchedCount" : 0,
 "modifiedCount" : 0,
 "upsertedId" : ObjectId("5fe1b4c8d9914101694100b7")
 } 

In this example, we try to find a document that has name: "Bubbles" but there’s none to be found.

This time we can see that the matchedCount is 0, and the modifiedCount is also 0. This means that none of the existing documents were updated.

We can also see that an upsertedId was returned, which means that a document was upserted.

Let’s take another look at the collection of documents:

db.pets.find()

Result:

 { "_id" : 1, "name" : "Wag", "type" : "Cow" }
 { "_id" : 2, "name" : "Bark", "type" : "Dog" }
 { "_id" : 3, "name" : "Meow", "type" : "Cat" }
 { "_id" : ObjectId("5fe1b4c8d9914101694100b7"), "name" : "Bubbles", "type" : "Fish" } 

We can see that a new document was inserted/upserted and it has the same ID as indicated above.

The upsert occurred because this time there were no matching documents to update (and so a new one was inserted/upserted instead).

If we hadn’t set upsert: true, that document wouldn’t have been inserted.

Upsert on Bulk Updates

When performing a bulk update, if you want to specify upsert: true, you need to use it with Bulk.find.upsert().

This can be used with the following write operations:

  • Bulk.find.replaceOne()
  • Bulk.find.updateOne()
  • Bulk.find.update()

The syntax goes like this:

Bulk.find(<query>).upsert().update(<update>);
Bulk.find(<query>).upsert().updateOne(<update>);
Bulk.find(<query>).upsert().replaceOne(<replacement>);

Example:

var bulk = db.pets.initializeUnorderedBulkOp();
bulk.find( { name: "Bruce" } ).upsert().replaceOne(
   {
     name: "Bruce",
     type: "Bat",
   }
);
bulk.execute();

Result:

 BulkWriteResult({
 "writeErrors" : [ ],
 "writeConcernErrors" : [ ],
 "nInserted" : 0,
 "nUpserted" : 1,
 "nMatched" : 0,
 "nModified" : 0,
 "nRemoved" : 0,
 "upserted" : [
 {
 "index" : 0,
 "_id" : ObjectId("5fe1c179d9914101694100dd")
 }
 ]
 }) 

We can see that one document was upserted. We can also see the _id that was generated for that document.

Now when we view the documents in our collection, we can see the new document that was upserted:

db.pets.find()

Result:

 
 { "_id" : 1, "name" : "Wag", "type" : "Cow" }
 { "_id" : 2, "name" : "Bark", "type" : "Dog" }
 { "_id" : 3, "name" : "Meow", "type" : "Cat" }
 { "_id" : ObjectId("5fe1b4c8d9914101694100b7"), "name" : "Bubbles", "type" : "Fish" }
 { "_id" : ObjectId("5fe1c179d9914101694100dd"), "name" : "Bruce", "type" : "Bat" }