While you might be familiar with MongoDB’s Aggregation Framework pipelines (primarily used for crafting intricate query results), a lesser-known capability is their application in updating documents.
This article will delve into the utilization of aggregation pipelines for efficient document updates.
Understanding the Basics
Let’s start with an example document representing a breakfast recipe:
{
"_id": {
"$oid": "recipe:toast"
},
"title": "Toast",
"calories_per_serving": 75,
"prep_time": 1,
"cook_time": 4,
"ingredients": [
{
"name": "bread",
"quantity": {
"amount": 4,
"unit": "slice"
},
"vegetarian": true
},
{
"name": "butter",
"quantity": {
"amount": 2,
"unit": "tablespoon"
},
"vegetarian": true
}
],
"directions": [
"Toast bread.",
"When both sides are an even golden brown, butter one side, care being taken to butter the edges.",
"Melt butter.",
"Serve hot."
],
"rating": [5,1,5,2,4,4,5,3,2,5,3,4,1,2,5,3],
"servings": 4,
"tags": ["bread", "quick", "vegetarian"],
"type": "Breakfast",
"vegetarian_option": true
}
Adding Calculated Fields
One powerful use case is the creation of pre-calculated fields. For example, what if we want to show our users a recipe’s “average rating” based on the user ratings in our rating
field.
To calculate and add a new rating_avg
field to a document (this also works for an update), you can send a aggregation query to updateOne()
like this:
db.examples.updateOne(
{ "_id": "recipe:toast" },
[{
$set: {
rating_avg: {
$round: [ { $avg: "$rating" }, 2]
}
}
}]
)
This will add a new rating_avg
field to the document:
{
...
"title": "Toast",
...
"rating_avg": 3.38
}
Here we used the $set
pipeline stage and two other operators to accomplish the average calculation: $avg
and $round
.
The $avg
operator is used to compute the average of numerical values within an array, such as the rating
field in this context. It calculates the “arithmetic mean” by summing up all the values and dividing the result by the total count of elements, providing the average rating.
In tandem with $avg
, the $round
operator is applied to round numerical values to a specified number of decimal places. Here, $round
is used on the outcome of $avg
to round the average rating to two decimal places, ensuring a more concise and standardized representation in the rating_avg
field.
To update this field for all documents in a collection that match a certain criteria, we can use updateMany()
:
db.examples.updateMany(
{ type: "Dessert" },
[{
$set: {
rating_avg: {
$round: [ { $avg: "$rating" }, 2]
}
}
}]
)
Handling Null Values
To avoid adding the rating_avg
field with a value of null
for documents without a rating
field, you can modify the query to use an $exists
query:
db.examples.updateMany(
{ rating: { $exists: true } },
[{
$set: {
rating_avg: {
$round: [ { $avg: "$rating" }, 2]
}
}
}]
)
Expressive Updates with $replaceWith
For more expressive update statements, another option is to use the $replaceWith
stage:
db.examples.updateOne(
{ _id: "recipe:toast" },
[{
$replaceWith: {
$setField: {
field: "rating_avg",
input: "$$ROOT",
value: {
$round: [ { $avg: "$rating" }, 2]
}
}
}
}]
)
This offers a clear and concise way to update fields.
Exploring Advanced Stages
MongoDB pipelines offer various stages for more advanced updates. For example, $lookup
enables SQL-like joins between documents in different collections (if they are in the same database). Another stage, $merge
, allows writing results directly to a collection. Note that $merge
must be the last stage in a pipeline.
Whether you’re adding pre-calculated fields or performing expressive updates, mastering MongoDB pipelines can enhance the efficiency of your document updates. Explore the different stages available and tailor them to your specific use cases for a seamless document update experience!