This article is part of Robert Sheldon's continuing series on Mongo DB. To see all of the items in the series, click here.
In the previous articles this series, I demonstrated various ways to retrieve document data from a MongoDB database, using both MongoDB Shell and MongoDB Compass. In this article, my focus shifts from retrieving data to updating data, which is an essential skill to have when working with MongoDB. Whether you access the data directly in a MongoDB database or build applications that rely on the data, you should have a good foundation in how to modify a collection’s documents. This article can help you build that foundation.
MongoDB provides multiple methods for modifying the documents in a collection. In this article, I focus on two of those methods: updateOne
and updateMany
. The methods are fairly similar to each other except that, as their names suggest, the updateOne
method can update only one document at a time, while the updateMany
method can modify any number of documents.
In this article, I demonstrate how to build update statements that use these two methods to modify data. The examples are based on MongoDB Shell, rather than on how the methods might be used in a programming language such as Java or Python. On the surface, the updateOne
and updateMany
methods might seem fairly basic, but when you start digging deeper into them, you’ll find that they’re not quite as straightforward as they appear.
This article takes you through the basics of using these methods to modify document data. As with most MongoDB topics, covering every aspect of the two methods requires more than a single article. However, I tried to provide you with the most important information so you can start using them in your own projects.
Note: For the examples in this article, I used the same MongoDB Atlas and MongoDB Compass environments I used for the previous articles in this series. Refer to the first article for more specifics about setting up these environments. If you want to try out the examples in this article, you should create the hr
database and employees
collection in that database. Each section provides the test data you’ll need to try out those examples.
Updating documents in a MongoDB collection
The updateOne
and updateMany
methods are two of the most common methods used to update data in MongoDB. You can incorporate either one into a statement that you can then run in MongoDB Shell. The statement’s basic syntax is essentially the same for both methods, except for the method name. For example, the following syntax shows the elements that make up an updateOne
statement:
1 |
db.collection.updateOne( { filter }, { update }, { options } ); |
The syntax might vary when used in other programming languages—as opposed to MongoDB Shell—but the basic concepts are much the same. In this case, the updateOne
statement is made up of the following components:
- db. System variable for referencing the current database and accessing the properties and methods available to the database object.
- collection. Placeholder for the target collection. For this article, we will be using the
employees
collection. - updateOne. A method available to the collection object for updating a single document in the specified collection.
- filter. Placeholder for the selection criteria that determine which document to update. This is similar to the filter used in a
find
statement. Iffilter
returns more than one document, MongoDB applies the update only to the first document returned from the collection. An empty document ({}
) means thatfilter
returns all documents in the collection, although MongoDB still applies the update only to the first returned document. Some MongoDB documentation refers to this element asquery
, rather thanfilter
. - update. Placeholder for the modifications that should be applied to the documents returned by
filter
. The above syntax shows this section enclosed in curly brackets, which indicate that you should pass in your update definition as an embedded document. However, you can instead pass in your argument as a limited aggregation pipeline, in which case, the argument is enclosed in square brackets. - options. Placeholder for one or more optional settings that can be included in an
updateOne
statement to better refine the query.
A statement that is based on an updateMany
method works much the same way as the updateOne
method. The only variation in the basic syntax is the method name:
1 |
db.collection.updateMany( { filter }, { update }, { options } ); |
The main difference between the updateOne
and updateMany
methods, other than their names, is the filter
element. With the updateOne
method, the update is applied only to the first document returned from the collection, no matter how many documents that filter
returns. With the updateMany
method, the update is applied to all documents that filter
returns.
You’ll be able to get a better sense of how these two methods work and the differences between them as we go through the examples in this article. You’ll also see a couple of the options in action so you can get a better sense of how they work. In the meantime, you can find more information about the updateOne
and updateMany
methods, as well as other methods for updating data, in the MongoDB topic Update Methods.
Performing a basic update
To help you get started with the updateOne
and updateMany
methods, we’ll begin with a couple basic examples of how to modify documents in a collection. For this article, we’ll be using the version of MongoDB Shell embedded in the MongoDB Compass GUI. In this way, we can view our results in the main Compass interface as we update the data MongoDB Shell, making it easier to see what has changed.
If you plan to try out these examples for yourself, make sure to first create the hr
database and employees
collection. You should then open the collection in the main Compass window so you can easily see your results as you work through the examples.
After you’ve set up your database and collection, go to MongoDB Shell and run the following use
statement to change the context to the hr
database:
1 |
use hr |
Next, you’ll need to add several documents to the collection so you have something to update. For this, you can use an insertMany
method. The method is available to the collection object for adding multiple documents to a collection. The following statement uses the method to add three documents to the employees
collection:
1 2 3 4 5 |
db.employees.insertMany([ { "_id": 101, "name": "Drew", "title": "Senior Developer", "department": "Development" }, { "_id": 102, "name": "Parker", "title": "Data Scientist", "department": "Development" }, { "_id": 103, "name": "Kerry", "title": "Marketing Manager", "department": "Marketing" } ]); |
The documents are passed into the insertMany
method as an array of embedded documents. Although these are very simple documents, they’re enough to demonstrate how to update data.
To run the insertMany
statement, copy and paste it to the MongoDB Shell command prompt and then press Enter. MongoDB should return a message indicating that the three documents have been added. Next, go to the Documents tab of the main Compass window and click the refresh button in the tab’s upper right corner. The tab should now display the three documents, as shown in the following figure.
With the data in place, you’re now ready to try out your first update statement. In the following example, the updateOne
method identifies the target document and changes the value of the title
field to Brand
Manager
:
1 2 3 4 |
db.employees.updateOne( { "_id" : 103 }, { $set: { "title" : "Brand Manager" } } ); |
The statement includes two embedded documents, which define the method’s filter
and update
elements, respectively:
- The
filter
element specifies that only the document with an_id
value of103
should be updated. - The
update
element uses the$set
operator to assign a new value to a field. In this case, the operator sets the value of thetitle
field toBrand
Manager
. The field name and its value are enclosed in curly brackets and separated by a colon.
When you run this statement, MongoDB Shell returns the following message, which indicates that one document matched the filter
criteria and one document was modified:
1 2 3 4 5 6 7 |
{ acknowledged: true, insertedId: null, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 } |
If you return to the Documents tab of the main window and click the refresh button, the Compass GUI should now display the following results, which show that the title
value in the third document has been updated.
Now let’s look at an example that uses the updateMany
method to modify documents. The following statement changes the value of the department
field to R&D
in the target documents:
1 2 3 4 |
db.employees.updateMany( { "department" : "Development" }, { $set: { "department" : "R&D" } } ); |
As with the previous example, the statement includes two embedded documents for defining the filter
and update
elements:
- The
filter
element specifies that only documents with adepartment
value ofDevelopment
should be updated. - The
update
element uses the$set
operator to set thedepartment
value toR&D
.
When you run this statement, MongoDB Shell returns the following message, which indicates that two documents match the search criteria and two documents have been modified:
1 2 3 4 5 6 7 |
{ acknowledged: true, insertedId: null, matchedCount: 2, modifiedCount: 2, upsertedCount: 0 } |
If you once again refresh the Documents tab of the main window, Compass should now display the following results, which show that the department
value in the first two documents has been updated.
As you can see, using the updateOne
and updateMany
methods to perform basic modifications on a document is fairly straightforward, and the two methods work much the same. You must first define the filter
element, followed by the update
element. That said, there is much more you can do with both of these methods.
Performing an Upsert
As I mentioned earlier in the article, the updateOne
and updateMany
methods let you specify optional settings to better refine your query. One of these options is upsert
, which controls how an updateOne
or updateMany
statement behaves if no documents match the filter
criteria.
The upsert
option takes a Boolean value, either true
or false
. When the option is set to true
, the statement performs one of the following actions:
- If
filter
returns a match, the statement updates the target document or documents. - If
filter
returns no matches, the statement inserts a new document, based on thefilter
andupdate
elements.
If the upsert
option is instead set to false
, the statement does not insert a new document into the collection, even if filter
returns no matches. This is the default setting and how an update statement behaves when the option is not specified.
Before we look at an example that uses the upsert
option, let’s delete the existing documents in the employees
collection so we’re starting with a clean slate. You can delete all the documents at once by running the following deleteMany
statement:
1 |
db.employees.deleteMany({}); |
Because the deleteMany
statement does not specify a filter
criteria—instead using only an empty set of curly brackets—the statement will delete all documents in the collection. This is a handy statement to remember when you’re learning about MongoDB or want to clean up your development or test environments. However, use extreme caution when using this statement in a production environment.
After you delete the collection’s documents, you can run the following insertMany
statement to add the original documents back into the collection:
1 2 3 4 5 |
db.employees.insertMany([ { "_id": 101, "name": "Drew", "title": "Senior Developer", "department": "Development" }, { "_id": 102, "name": "Parker", "title": "Data Scientist", "department": "Development" }, { "_id": 103, "name": "Kerry", "title": "Marketing Manager", "department": "Marketing" } ]); |
Now let’s try out the upsert
option when updating a document. The following updateOne
statement attempts to update a document with an _id
value of 104
:
1 2 3 4 5 |
db.employees.updateOne( { "_id" : 104 }, { $set: { "name": "Casey", "title": "Copy Writer", "department": "Marketing" } }, { upsert: true } ); |
Unlike the previous examples, the method now includes three embedded documents:
- The
filter
element specifies that only the document with an_id
value of104
should be updated. - The
update
element uses the$set
operator to set thename
,title
, anddepartment
fields to the valuesCasey
,Copy
Writer
, andMarketing
, respectively. - The
upsert
element sets theupsert
option totrue
. Notice that the option is added as an embedded document, just like thefilter
andupdate
elements.
When you run the updateOne
statement, MongoDB Shell returns the following message:
1 2 3 4 5 6 7 |
{ acknowledged: true, insertedId: 104, matchedCount: 0, modifiedCount: 0, upsertedCount: 1 } |
The message indicates that one document has been upserted and the ID for that document is 104
. It also indicates that there were no matched documents. If you once again refresh the Documents tab of the main window, the Compass GUI should display the results shown in the following figure, which now include a fourth document.
MongoDB added the fourth document to the collection because no documents matched the filter
criteria in the original query. An updateMany
statement works much the same way, as shown in the following example:
1 2 3 4 5 |
db.employees.updateMany( { "name": "Jesse", "title": "Senior Developer" }, { $set: { "department" : "R&D" } }, { upsert: true } ); |
Like the previous example, this statement includes three embedded documents:
- The
filter
element specifies that only documents with aname
value ofJesse
and atitle
value ofSenior
Developer
should be updated. - The
update
element uses the$set
operator to set thedepartment
value toR&D
. - The
upsert
element sets theupsert
option totrue
.
When you run the updateMany
statement, MongoDB Shell returns the following message:
1 2 3 4 5 6 7 |
{ acknowledged: true, insertedId: ObjectId('65de345e261378a0b003c281'), matchedCount: 0, modifiedCount: 0, upsertedCount: 1 } |
In this case, the message indicates that one document has been upserted and that the document’s ID is a GUID, unlike the simple integers used for the other documents. MongoDB generated the _id
value automatically because no value had been specified in the filter
or update
element. When you refresh the Documents tab, the Compass GUI now shows the following documents:
As you can see, a fifth document was added to the collection, and its _id
value is a GUID. Because the filter
element in the updateMany
statement did not return any matches, MongoDB inserted a new document using the information from the filter
and update
elements.
Updating an embedded document
In the previous examples, the statements updated simple string fields that required you only to specify the field name and new value. The process works much the same for embedded documents, except that you must qualify the names of the subfields to ensure the data gets modified as expected.
To see how this works, first delete the documents in the employees
collection, and then run the following insertMany
statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
db.employees.insertMany([ { "_id": 101, "name": "Drew", "position": { "title": "Senior Developer", "department": "Development" } }, { "_id": 102, "name": "Parker", "position": { "title": "Data Scientist", "department": "Development" } }, { "_id": 103, "name": "Kerry", "position": { "title": "Marketing Manager", "department": "Marketing" } } ]); |
The statement defines three documents that each contain the position
field. The field’s value is an embedded document that includes the title
and department
subfields. Now let’s try to update one of the subfields, using the following updateOne
statement:
1 2 3 4 |
db.employees.updateOne( { "_id" : 103 }, { $set: { "position.title" : "Brand Manager" } } ); |
The filter
element specifies that only the document with an _id
value of 103
should be updated. This is followed by the update
element, which uses the $set
operator to set the title
value to Brand
Manager
. Notice that you must qualify the subfield name by preceding it with the position
field name, followed by a period. In this way, MongoDB knows exactly where to find the target subfield.
After you run the statement, you can refresh the Documents tab to review the results, which are shown in the following figure.
Notice that the title
subfield has been updated in the third document. As this example demonstrates, you must qualify the field names in embedded documents when referencing them in an update
element. This is also true if you specify an embedded field in the filter
element, as in the following updateMany
statement:
1 2 3 4 |
db.employees.updateMany( { "position.department" : "Development" }, { $set: { "position.department" : "R&D" } } ); |
This time, the filter
element specifies that only documents with a position.department
value of Development
should be updated. Only two documents meet this criteria. The update
element then uses the $set
operator to set their position.department
value to R&D
. After you run the statement, you can again refresh the Documents tab to review the results, which are shown in the following figure.
That’s all there is to working with embedded documents. That main point to remember is that you need to qualify the field names when they come from an embedded document.
Updating an array
Updating an array value is a little trickier than with an embedded document. For this, you’ll need to use the arrayFilters
option, which I’ll demonstrate shortly. But first, you should again delete the documents in the employees
collection and this time run the following insertMany
statement:
1 2 3 4 5 |
db.employees.insertMany([ { "_id": 101, "name": "Drew", "skills": [ "Java", "SQL", "Python", "PHP" ] }, { "_id": 102, "name": "Parker", "skills": [ "Java", "Csharp", "Python", "R" ] }, { "_id": 103, "name": "Kerry", "skills": [ "Csharp", "SQL", "Swift", "C" ] } ]); |
The statement adds three documents to the collection. Each document includes the skills
array, which contains several string values describing the employee’s skills.
Now let’s look at how to modify the array. Suppose you want to update a skill in one of the documents, in this case, the one with an _id
value of 101
. Your goal is to change the PHP
value in the skills
array to JavaScript
. For this, you can run the following updateOne
statement:
1 2 3 4 5 |
db.employees.updateOne( { "_id": 101 }, { $set: { "skills.$[skill]": "JavaScript" } }, { arrayFilters: [ { "skill": "PHP" } ] } ); |
As you can see, the updateOne
method defines three elements:
- The
filter
element specifies that only the document with an_id
value of101
should be updated. - The
update
element uses the$set
operator to specify that a value in theskills
array should be changed toJavaScript
. The value to be changed is represented by theskill
variable, which is constructed as an identifier operator. An identifier operator is an element enclosed in square brackets and preceded by a dollar sign, all of which is then tagged onto the array name. - The
arrayFilters
element essentially defines theskill
variable by assigning the old value,PHP
, to the variable. In this way, the variable can be used in theupdate
element to indicate which value should be changed within the array. Note that the variable (identifier) name must begin with a lowercase letter and can contain only alphanumeric characters.
After you run the updateOne
statement, you can again refresh the Documents tab and review the results, which are shown in the following figure. As you can see, the original array value, PHP
, has been replaced with JavaScript
.
You can also use the arrayFilters
option in an updateMany
statement, once again specifying a variable that identifies the value to be changed. For example, the follow statement updates the documents that contain the value Csharp
in the skills
array:
1 2 3 4 5 |
db.employees.updateMany( { "skills": "Csharp" }, { $set: { "skills.$[skill]": "C#" } }, { arrayFilters: [ { "skill": "Csharp" } ] } ); |
As in the previous example, the statement includes three elements:
- The
filter
element specifies that the documents must contain the valueCsharp
in theskills
array. - The
update
element changes the array valueCsharp
toC#
, using theskill
variable to reference the original value. - The
arrayFilters
element assigns the valueCsharp
to theskill
variable so it can be used in theupdate
element.
The statement updates two documents. You can confirm this by again refreshing the Documents tab in the main Compass window. The following figure shows the updated documents.
Updating an array value is fairly straightforward once you figure out how an identifier operator works and how to apply the arrayFilters
option. To this end, you might find it useful to review the MongoDB topic $[<identifier>] to get a better sense of how to use both an identifier operator and the arrayFilters
option.
Updating with an aggregation pipeline
The examples up to this point have used an embedded document for the update
element, which defines the modifications that should be applied to the filtered documents. However, you can instead specify an aggregation pipeline for the update
element, using any of the following three stages:
- The
$addFields
stage (or its alias$set
)—adds fields to the documents in the pipeline. - The
$project
stage (or its alias$unset
)—reshapes the fields in the pipeline documents. - The
$replaceRoot
stage (or its alias$replaceWith
)—replaces each document in the pipeline with the specified embedded document.
An aggregation pipeline can provide you with more flexibility when modifying your documents, compared to the statements we’ve created so far. To see how this works, first delete the documents in the employees
collection, and then run the following insertMany
statement, which creates three simple documents:
1 2 3 4 5 |
db.employees.insertMany([ { "_id": 101, "name": "Drew", "title": "Senior Developer", "department": "Development" }, { "_id": 102, "name": "Parker", "title": "Data Scientist", "department": "Development" }, { "_id": 103, "name": "Kerry", "title": "Marketing Manager", "department": "Marketing" } ]); |
Next, we’ll create an insertMany
statement that restructures each document and adds a calculated field, as shown in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
db.employees.updateMany( { }, [ { $set: { "position": { "title": "$title", "department": "$department" } } }, { $set: { "position.division": { $switch: { branches: [ { case: { $eq: [ "$position.department", "Development"] }, then: "Eastern" }, { case: { $eq: [ "$position.department", "IT Services"] }, then: "Eastern" }, { case: { $eq: [ "$position.department", "Marketing"] }, then: "Western" }, { case: { $eq: [ "$position.department", "Sales"] }, then: "Western" } ], default: "Central" } } } }, { $unset: [ "title", "department" ] } ] ); |
The statement begins by defining the filter
element as an empty document, which means that the update will be applied to all documents in the collection. The filter
element is followed by an update
element that defines an aggregation pipeline, as indicated by the square brackets. The pipeline includes the following three stages:
- The first
$set
stage (which is an alias for$addFields
) adds a field namedposition
. The field’s value is an embedded document that contains thetitle
anddepartment
subfields. The originaltitle
anddepartment
fields provide the values for the new subfields. This is done by referencing the original fields and preceding their names with a dollar sign. - The second
$set
stage creates a subfield nameddivision
within theposition
field. The value of the subfield is determined by the$switch
operator, which is used to create a conditionalcase
expression. The expression defines thebranches
array and its four embedded documents. Each document uses thecase
operator to specify a value for thedivision
subfield, based on a value in thedepartment
subfield. For example, the first embedded document specifies that thedivision
subfield should be assigned the valueEastern
if thedepartment
value isDevelopment
. Thebranches
array is then followed by adefault
element that designatesCentral
as the default value if no conditions are met. - The final
$unset
stage specifies that the originaltitle
anddepartment
fields should be removed from each document. These fields are no longer needed because their values have been reassigned to theposition
subfields in the first$set
stage.
After you define you’re statement, you can run it in MongoDB Shell and view the results on the Documents tab. The following figure shows the collection’s documents after they’ve been updated and the tab refreshed.
As you can see, each document now contains the position
field and its value, which is an embedded document. The embedded document includes the original title
and department
fields, along with the new division
field.
Although we were working with very simple documents for this example, it should still give you a sense of how useful it might be to include an aggregation pipeline in your updateOne
and updateMany
statements, despite having only three stages to work with.
Getting started with MongoDB updates
The ability to update data is one of the core skills you should have when working with MongoDB. To this end, the updateOne
and updateMany
methods provide a good place to start learning how to modify data. However, they’re not the only methods available. For example, MongoDB also supports the replaceOne
, findOneAndReplace
, findOneAndUpdate
, and findAndModify
methods, any of which you might find useful at times. In addition, you can update data directly in the Compass GUI by modifying a document’s individual values.
Despite the various options available in MongoDB for updating data, the updateOne
and updateMany
methods will go a long way in helping you get started with modifying data. You might find it useful to check out the MongoDB documentation that covers these methods, as well as other methods for updating data. When it comes to modifying data, however, you must proceed cautiously to ensure you don’t make incorrect changes, especially in a production environment. The better you understand how to update data in a MongoDB database, the less likely you are to inadvertently wipe out an entire collection of documents.
Load comments