MongoDB Update With and Without Set Operator

MongoDB Update

A quick update to the developer notebook: have you tried using the MongoDB update command with-and-without the $set operator?

Generally, when you want to update a document in a collection, you will select from:

  • updateMany: updates multiple documents in a collection based on a filtered match
  • updateOne: updates the first document in a collection based on a filtered match
  • replaceOne: replaces the schema of a document document based on a filtered match

However, the update operation is also available, and it has significantly different behavior depending on whether or not you specify the $set operator in your query. For reference: MongoDB Update.

The Setup

Let’s suppose we have some clients that are described by the following JSON array:

[
  {
    "name":"ABC Corp",
    "address":"123 ABC Lane"
  },
  {
    "name":"XYZ Corp",
    "address":"789 XYZ Street"
  }
]

Populate a clients DB with a collection of these documents and you will have the following:

{
"_id" : ObjectId("5fa..."),
"name" : "ABC Corp",
"address" : "123 ABC Lane"
}
{
"_id" : ObjectId("5fa..."),
"name" : "XYZ Corp",
"address" : "789 XYZ Street"
}

updateOne and updateMany Operations

The updateOne and updateMany operations require the $set operator to be specified. Things work if you include $set:

> db.blogclients.updateOne(
{"_id": ObjectId("5fa...")}, 
{$set: {"name": "New Corp"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.blogclients.find().pretty()
{
"_id" : ObjectId("5fa..."),
"name" : "ABC Corp",
"address" : "123 ABC Lane"
}
{
"_id" : ObjectId("5fa..."),
"name" : "New Corp",
"address" : "789 XYZ Street"
}

But, if you were to try and execute this command without the $set, you’d receive:

> db.blogclients.updateOne(
{"_id": ObjectId("5fa...")}, 
{"name": "Another New Corp"})
uncaught exception: Error: the update operation document must contain atomic operators :
DBCollection.prototype.updateOne@src/mongo/shell/crud_api.js:565:19
@(shell):1:1

The same is true for updateMany. You could for example set a property for all documents where the name property is specified:

> db.blogclients.updateMany(
{"name":{$ne:""}}, {$set:{"confirmed": true}})
{ "acknowledged" : true, "matchedCount" : 2, "modifiedCount" : 2 }
> db.blogclients.find().pretty()
{
"_id" : ObjectId("5fa..."),
"name" : "ABC Corp",
"address" : "123 ABC Lane",
"confirmed" : true
}
{
"_id" : ObjectId("5fa..."),
"name" : "New Corp",
"address" : "789 XYZ Street",
"confirmed" : true
}

Though, if you were to try it without the $set operator, you’d get the same error as with the updateOne command:

> db.blogclients.updateMany(
{"name":{$ne:""}}, {"prepared": true})
uncaught exception: Error: the update operation document must contain atomic operators :
DBCollection.prototype.updateMany@src/mongo/shell/crud_api.js:655:19
@(shell):1:1

So, we see that updateOne and updateMany cannot be executed without specifying the $set operator.

 

MongoDB Update

Unlike updateOne and updateMany, update does support operations without the $set operator.

If you specify the $set operator, then it will update the first document in the collection that it matches. Basically, it behaves like updateOne:

> db.blogclients.update({"confirmed": true},{$set:{launched: false}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blogclients.find().pretty()
{
"_id" : ObjectId("5fa..."),
"name" : "ABC Corp",
"address" : "123 ABC Lane",
"confirmed" : true,
"launched" : false
}
{
"_id" : ObjectId("5fa..."),
"name" : "New Corp",
"address" : "789 XYZ Street",
"confirmed" : true
}

Notice how the “launched” property is only specified the first matched document of “confirmed” equal to “true”.

Okay, so now, I want to set the “reviewed” property of the first “confirmed= true” document to false. But, I don’t specify the $set operator. Unlike its “update” cousins, this is totally legal syntax, and as you see, it behaves more like the replaceOne function. That is, it completely replaces the schema of the first matched element:

> db.blogclients.find().pretty()
{
"_id" : ObjectId("5fa..."),
"name" : "ABC Corp",
"address" : "123 ABC Lane",
"confirmed" : true,
"launched" : false
}
{
"_id" : ObjectId("5fa..."),
"name" : "New Corp",
"address" : "789 XYZ Street",
"confirmed" : true
}
> db.blogclients.update(
{"confirmed": true},{launched: false})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blogclients.find().pretty()
{ "_id" : ObjectId("5fa..."), "launched" : false }
{
"_id" : ObjectId("5fa..."),
"name" : "New Corp",
"address" : "789 XYZ Street",
"confirmed" : true
}

 

Maybe what you wanted…but maybe not. For me, I’ll generally just use the replaceOne command if I intend to update a document’s schema in this manner.

 

Thanks for reading.

 

 



Categories: MongoDB, Software Development

Tags:

Leave a Reply

%d