DEV Community

Cover image for Learn MongoDB: Update Documents
Paras πŸ§™β€β™‚οΈ
Paras πŸ§™β€β™‚οΈ

Posted on • Edited on

Learn MongoDB: Update Documents

After learning about querying documents, it's time to learn how to update documents. If you know how to find your data, it will make it easy for you to update them as well. So without wasting time, let's get started !


Table of Content :


Update methods in mongo

Methods that helps us with updating documents in mongodb are:

  • updateOne
  • updateMany

In this post, we will work with trainers collection. Enough of pokemons !! For the start, we will keep the structure simple:

{
   name: "Ash",
   age: 18,
   pokemons: [
      { name: "pikachu", type: "electric", level: 16 },
      { name: "charizard", type: "fire", level: 30 },
      { name: "squirtle", type: "water", level: 12 }
   ],
   badges: ["boulder badge", "cascade badge"],
   exp: 200,
   currentTown: "Pallet Town"
}
Enter fullscreen mode Exit fullscreen mode

We will add data and change structure according to our need to make it easy while learning to update data.

Updating Documents

One important operator that you should know while updating documents is $set. If we try to update documents without this operator, it will result in error or unexpected behavior.

Update methods take first parameter as your document filter, to filter the documents that you want to update and second parameter as your update that you want to apply to the filtered documents. There is a third parameter but we will learn about it later in the post.

Basics

# update the name of a trainer
> db.trainers.updateOne(
    {name: "Ash"},
    {$set: {name: "Misty"}}
)

# update age of all the trainers to 18
> db.trainers.updateMany({}, {$set: {age: 18}})

# add a new field to all the trainers 
> db.trainers.updateMany(
    {},
    {$set: {hasPokemon: false}}
)
Enter fullscreen mode Exit fullscreen mode

You don't know but mongo is very smart and if you try to update a field with the same value it already has, it ignores it (Like a pro)

Update Operators

Update operators help us with updating documents in various ways.

Operators:

  • $inc : Increment or decrement numeric values
  • $unset : Remove a field from document
  • $rename : Rename a field
  • $min : To update a numeric field with the given value if it is less than the existing value
  • $max : To update a numeric field with the given value if it is more than the existing value

  • $upsert : If the document exists, then update it, else create the document

  • $mul : To perform multiplication on numeric fields

There are other operators that help you work with arrays as well and we will discuss them later.

So, let's see some examples for the above operators and their usage

Trainers Document Structure

{
   name: "Ash",
   age: 18,
   pokemons: [
      { name: "pikachu", type: "electric", level: 16 },
      { name: "charizard", type: "fire", level: 30 },
      { name: "squirtle", type: "water", level: 12 }
   ],
   badges: ["boulder badge", "cascade badge"],
   exp: 200,
   currentTown: "Pallet Town",
   bagItems: {
      pokeballs: 8,
      heals: 10
   },
   isFunny: true
}
Enter fullscreen mode Exit fullscreen mode

Examples :

  • Increment & decrement numeric values in document
# increment the experience of "Ash" by 50
> db.trainers.updateOne({name: "Ash"}, {$inc: {exp: 50}})

# decrease the number of pokeballs in Ash's bag by 2
> db.trainers.updateOne({name: "Ash"}, {$inc: {"bagItems.pokeballs": -2}})
Enter fullscreen mode Exit fullscreen mode
  • Updating numeric values according to current value with $min and $max
# update the number of heals in Ash's bag if current no. of heals are less than the update value
# this will only update if the passed value is less than the value in db
> db.trainers.updateOne({name: "Ash"}, {$min: {"bagItems.heals": 25}})

# update exp of brock to 300 only if the new exp. is greater than the current exp
> db.trainers.updateOne({name: "Brock"}, {$max: {exp: 300}})
Enter fullscreen mode Exit fullscreen mode
  • Performing multiplication on numeric field
# let's multiply Ash's exp (and make him invincible !!)
> db.trainers.updateOne({name: "Ash"}, {$mul: {exp: 100}})
Enter fullscreen mode Exit fullscreen mode
  • Let's see operations on fields and start with removing a field from doc
# remove "isFunny" field from all the docs
> db.trainers.updateMany({}, {$unset: {isFunny: ""}})

# NOTE 1: the value you pass to the field can be anything. Field name is the important value here
# NOTE 2: You can remove nested fields in docs as well e.g. "bagItems.heals"
Enter fullscreen mode Exit fullscreen mode
  • Renaming a field
# rename "exp" field to "experience"
> db.trainers.updateMany({}, {$rename: {exp: "experience"}})
Enter fullscreen mode Exit fullscreen mode
  • Updating a document and if doesn't exist, then create it.
> db.trainers.updateOne(
    {name: "Paul"}, 
    {$set: {age: 19, exp: 200}},
    {upsert: true}
)
Enter fullscreen mode Exit fullscreen mode

Here you see the third argument that update method accepts. It is like options that you can change for a query. By default upsert is set to false. By making upsert true, we got the update or create behavior in our update.

Result of the above query

{
   name: "Paul",
   age: 19,
   exp: 200
}
Enter fullscreen mode Exit fullscreen mode

As you can see, mongo not only creates the document with the new fields but also adds the field which we passed as filter in first argument. Cool isn't it (I told you mongo is smart)

Updating Arrays

Updating arrays is a bit different than updating other fields in a document or nested documents. But it's not rocket science.

Let's start with some operators that can help us do basic operations with arrays.

  • $push ; add element to array in a doc
  • $pull : remove the specified element from array in a doc
  • $pop : remove element from array either from end or start
  • $each : helps to push multiple elements at once instead of just one
  • $addToSet : treats an array like a set and only add the value if it doesn't exists

Example Time :

# add a new pokemon to Ash's pokemons
> db.trainers.updateOne(
    {name: "Ash"},
    {$push: { pokemons: { name: "bulbasaur", type: "grass", level: 17 }}}
)

# add more than one pokemon to Ash's pokemons with $each
> db.trainers.updateOne(
    {name: "Ash"},
    {
        $push: { 
            pokemons: { $each: [
                {name: "butterfree", type: "bug", level: 20},
                {name: "piggeotto", type: "flying", level: 14},
                {name: "Krabby", type: "water", level: 13}
            ]}
        }
    }
)

# remove a badge from Ash's badge array (because we are evil)
> db.trainers.updateOne(
    {name: "Ash"},
    {$pull: {badges: "boulder badge"}}
)

# let's also take away a pokemon from Ash (for fun)
> db.trainers.updateOne(
    {name: "Ash"},
    {$pull: {pokemons: {name: "krabby"}}}
)

# pop a pokemon from misty from the end
> db.trainers.updateOne({name: "Misty"}, {$pop: {pokemons: 1}})

# pop a pokemon from misty from the start
> db.trainers.updateOne({name: "Misty"}, {$pop: {pokemons: -1}})

# we let's give a badge to Brock but we need to make sure he doesn't have two badges of same type. So we need to treat array like a set
> db.trainers.updateOne(
    {name: "Brock"}, 
    {$addToSet: {badges: "Thunder Badge"}}
)

# try making the above query again, and you will notice, it will not add another Thunder Badge to Brock's badges.
Enter fullscreen mode Exit fullscreen mode
  1. Combine $addToSet with $each to add multiple unique values at once
  2. Pulling remove all matching elements from array, not just a single match.

More array operations

There are other operations that are related to positions in array. May be you want to update the fourth element in array (at 3rd index), or you want to update all the matched elements in an array and not just one. There are many situations like that.

Working with index

# Increment the level of first pokemon of Ash by 1
> db.trainers.updateOne({name: "Ash"}, {$inc: {"pokemons.0.level": 1}})
Enter fullscreen mode Exit fullscreen mode

We can find a specific element in array and update that specific element without knowing its index.

$ : Instead of passing an index, we can use this positional operator to refer to the matched results from our filter. See the example below to understand.

Query and update the matched element in array

# add a health field to all the trainer's pikachu.
> db.trainers.updateOne(
    {"pokemon.name": "pikachu"}, # query document
    {$set: {"pokemons.$.health": 150}} # update to apply
)

# '$' operator will figure out which element of the query document matched and then apply update on them
Enter fullscreen mode Exit fullscreen mode

NOTE: $ operator only updates the first element and not all elements in array. What that means ? It means that if a trainer has more than one pikachu, only the first pikachu in his pokemons array will be updated.

Using array filters option

The above problem with positional operator that it only updates the first match in array can be solved easily by using array filters. It helps you choose all the documents that you want to update. Let's make a good situation for query and solve it.

Suppose, we want to find trainers that have experience of 200 or above and teach their grass type pokemons "Solar Beam" move. A trainer can have multiple grass type pokemons, so we have to teach all grass type pokemons "Solar Beam" and not just one.

Query :

> db.trainers.updateMany(
    {experience: {$gte: 200}},
    {$set: {"pokemons.$[poke].move": "Solar beam"}},
    {applyFilters: [{"poke.type": "grass"}]}
)
Enter fullscreen mode Exit fullscreen mode

Explanation :

  • First is our document filter, to filter documents from collection
  • Next is our update. As you can see, we used $[poke] syntax. Here, poke is the identifier which will help us filter docs in array filters. You can name it anything. I named it poke , you can use something like elem , el etc.
  • At last, we have our object in which we define our array filters.
  • use applyFilters field to define filters for different identifiers you specified in $set
  • You can only have one filter for each identifier.
// this won't work and not allowed
[{"poke.type": "grass"}, {"poke.level": {$gte: 14}}]

// instead use $and, $or and other operators
[{
   $and: [
    {"poke.type": "grass"}, 
    {"poke.level": {$gte: 14}}
    ]
}]
Enter fullscreen mode Exit fullscreen mode

Refer to the following in docs for more details

Note that in docs update method is used and not updateOne and updateMany. Therefore, there when they want to use updateMany, they pass { multi: true } in options.


Finally chapter of update is closed ! If you have any questions, ask them in comments. I will try my best to answer them.

Next Post : Delete Documents

Prev Post : Query Documents - Part II

Top comments (0)