Welcome to the second post in my “MongoDB Animated 🍩” series, where I provide animated examples and explanations for MongoDB operations that I don’t ever want to google again.

Introduction

In this post, we’ll review different strategies for updating elements from embedded arrays.

I’ll use the same example collection used in the previous article in this series. It consists of a simple collection of “donut combos” from a donut shop. Each combo has a name and an array of donuts that will be included if the customer chooses that combo. Here’s the complete schema:

// donutCombo Schema
{
    name: { type: String, required: true },
    active: { type: Boolean, required: true },
    donuts: [{
        color: { type: String },
        glazing: { type: Boolean }
    }]
}

Updating elements

Updating all elements with $[]

Using the $set operator combined with the all positional operator $[] allows us to update all the array elements.

For example, let’s say we want to take away the glazing from all donuts in all active documents:

db.donutCombos.updateMany({ active: true }, {  
    $set: {  
        'donuts.$[].glazing': false 
    }  
});

This sort of operation can come in handy when you migrate to a new schema and your documents have embedded arrays. For example, if you needed to add a new property with a default value to all the array elements.

Another way to take away the glazing could be by removing the property glazing from all donuts in all active documents. This can be done using the $unset operator in combination with the all positional operator $[]:

db.donutCombos.updateMany({ active: true }, { 
    $unset: { 
        'donuts.$[].glazing': 1 
    } 
});

Notice that, inside the $unset object, we must define the key (the property we want to remove) and the value, which doesn’t impact the operation. In this case, we used “1”, but you can also use an empty string, for example: 'donuts.$[].glazing': ''.

Updating the first element with $

You may need to update only the first element of your array. In that case, you should use the positional operator $. This positional operator will allow us to apply changes to the first element that matches the query document (the first parameter of the update method). The array field must be a part of the query document.

By combining the $ positional operator with $set, we can update properties from the first array element that matches our query document.

In the following example we will update the first white donut in every document and change it’s color to “pink”.

db.donutCombos.updateMany({ 'donuts.color': 'white' }, { 
    $set: { 
        'donuts.$.color': 'pink'
    } 
});

The $ positional operator can also be combined with $unset to remove a property from the first array element that matches our query document:

So to try that, let’s remove the color from the first white donut found on each document.

db.donutCombos.updateMany({ 'donuts.color': 'white' }, { 
    $unset: { 
        'donuts.$.color': 1 
    } 
});

Updating elements that match a filter with $[<identifier>]

To update a set of elements matching certain filters, we must use the filtered positional operator $[<identifier>] where <identifier> is a placeholder for a value that represents a single element of the array.

We must then use the third parameter (options) of the updateMany method to specify a set of arrayFilters. Here we will define the conditions each array element we want to update must meet to be updated.

In the next example, we will change every white donut color to green, only in the active documents.

db.donutCombos.updateMany({ active: true }, { 
    $set: { 
        "donuts.$[donut].color": "green" 
    } 
}, 
{ 
    arrayFilters: [{ "donut.color": "white" }] 
});

Combining the filtered positional operator $[<identifier>] (or in our example $[donut]) with the operator $unset, we can remove a property from all elements in the array that match our arrayFilter criteria.

For example, we could remove the color of every white donut in every active document:

db.donutCombos.updateMany({ active: true }, { 
    $unset: { 
        "donuts.$[donut].color": 1
    } 
}, 
{ 
    arrayFilters: [{ "donut.color": "white" }] 
});

Try it yourself

I created a repo to try MongoDB queries in memory using Node.js with Jest and MongoDB Node driver. I used tests to execute the query and verify that everything was correctly updated. I also included a logger that prints the updated documents in the console displaying the changes that were applied using diff highlight syntax:

The difference of a MongoDB document after being updated

You can find the examples I included in this article in the tests folder:

Resources

For more info about updating arrays, here are a few resources from MongoDB’s official docs:

Was this post useful? 💬

I’m interested in your feedback. Was this post useful? Would you like me to cover any other operation so you don’t ever have to google it again?

This post is also available on DEV.