In this series we will explore different operators in the MongoDB Aggregation Framework via the context of a collection of recipes.
MongoDB Aggregations Series
- MongoDB Aggregations: Finding Cooking Times with $min and $max
- MongoDB Aggregations: Organizing Recipes by Meal Type with $group
Let’s say we’re making a brand new recipes website. Knowing the cooking times can help us categorize recipes into quick meals, slow-cooked dishes, or anything in between. MongoDB’s $min
and $max
aggregation operators allow you to extract minimum and maximum values across documents, making it easy to answer questions like:
“What’s the quickest recipe?” or “What’s the longest cooking recipe?”
In this post, we’ll walk through how to use $min
and $max
to find minimum and maximum cooking times within a recipe collection.
Why Use $min
and $max
for Cooking Times?
To start off let’s take into account three possible User Stories for our new website:
“As a User I want to create recipe filters …”
For busy users who need quick meal ideas
“As a User I want to highlight slow-cooked recipes …”
For special categories like soups, stews, or roasts
“As a Content Creator I want to use an analytics feature, to provide insights on average cooking times or to recommend recipes …”
For an internal user that helps generate content for our site.
With MongoDB, you can do all of this directly within your aggregation pipeline, saving time and improving efficiency.
Setting Up the Data
Let’s say you have a recipe
collection with documents structured something like this:
{
"_id": {
"$oid": "636821387dd21c28fda4939f"
},
"title": "Split Pea Soup",
"calories_per_serving": 158,
"prep_time": 10,
"cook_time": 300,
"ingredients": [
{
"name": "peas",
"quantity": {
"amount": 1,
"unit": "cup"
},
"vegetarian": true
},
...
{
"name": "ham hock",
"quantity": 1,
"vegetarian": false,
"optional": true
}
],
"directions": [
"Pick over and wash the vegetable and soak several hours or overnight.",
"Drain and add measured water and small onion, with ham hock if desired.",
...
],
"rating": [4,4,4,4,2,5,3],
"rating_avg": 3.71,
"servings": 6,
"tags": ["soup", "easy", "peas", "vegetarian"],
"type": "Dinner",
"vegetarian_option": true
}
Each recipe document includes a cook_time
field representing the cooking time in minutes. Our goal is to find the shortest and longest cooking times across all recipes.
Using $min
and $max
in an Aggregation Pipeline
MongoDB’s aggregation framework lets us efficiently calculate these values across documents using $group
, $min
, and $max
. Here’s a breakdown of how each operator works:
$min
: Finds the smallest value in a specified field.$max
: Finds the largest value in a specified field.$group
: Groups documents by a specific field or no grouping at all (i.e., bynull
) to apply aggregations across the entire collection.
Let’s build an aggregation pipeline to find the minimum and maximum cooking times for all recipes.
Step 1: Finding the Minimum Cooking Time
To find the shortest cooking time in the collection, we’ll set up an aggregation pipeline that groups all documents and uses $min
to return the smallest cook_time
value.
> db.recipes.aggregate([
{
$group: { _id: null, minCookTime: { $min: "$cook_time" } },
},
])
$group
We’re grouping by null
, which means all documents are aggregated together rather than by a specific field.
minCookTime: { $min: "$cook_time" }
We’re calculating the minimum cook_time
across all documents in the collection and setting it as minCookTime
in the output (note: this will not update your actual documents, just the output).
Output Example:
{ "_id": null, "minCookTime": 15 }
This output tells us the shortest cooking time in our recipe collection is 15 minutes.
Step 2: Finding the Maximum Cooking Time
Now, let’s find the longest cooking time in the collection. We’ll use a similar pipeline, replacing $min
with $max
:
> db.recipes.aggregate([
{ $group: { _id: null, maxCookTime: { $max: "$cook_time" } } },
])
maxCookTime: { $max: "$cook_time" }
This calculates the maximum cook_time
across all documents and sets it as maxCookTime
.
Output Example:
{ "_id": null, "maxCookTime": 360 }
This output indicates the longest cooking time in our collection is 360 minutes (6 hours).
Step 3: Combining Minimum and Maximum Calculations
To get both minimum and maximum cooking times in a single query, we can combine $min
and $max
in one pipeline stage:
> db.recipes.aggregate([
{
$group: {
_id: null,
minCookTime: { $min: "$cook_time" },
maxCookTime: { $max: "$cook_time" },
},
},
])
This pipeline groups all documents together and calculates both the minimum and maximum cooking times using $min
and $max
in the same $group
stage makes it more efficient by reducing the need to run multiple queries.
Output Example:
{
"_id": null,
"minCookTime": 15,
"maxCookTime": 360
}
Step 4: Calculating Total Time (Prep + Cook Time)
Often, users are interested in the total time it takes to make a recipe (including both preparation and cooking times). We can modify our pipeline to calculate total_time
by adding prep_time
and cook_time
for each recipe document. This involves using $addFields
to calculate a combined total_time
field before applying $min
and $max
.
Here’s how you could do it:
> db.recipes.aggregate([
{ $addFields: { total_time: { $add: ["$prep_time", "$cook_time"] } } },
{
$group: {
_id: null,
minTotalTime: { $min: "$total_time" },
maxTotalTime: { $max: "$total_time" },
},
},
])
$addFields
We add a total_time
field to each document, which is the sum of prep_time
and cook_time
.
minTotalTime
and maxTotalTime
: Using $min
and $max
on total_time
, we calculate the shortest and longest total times across all recipes.
Output Example:
{
"_id": null,
"minTotalTime": 25,
"maxTotalTime": 370
}
This output tells us that the shortest recipe (prep + cook) time is 25 minutes, and the longest is 370 minutes.
Practical Applications for Total Time Calculations
With total times at your disposal, you can build more user-friendly features:
Quick Recipe Suggestions
Use minTotalTime
to show fast recipes under 30 minutes.
Long Cooking Recipes
Filter recipes based on maxTotalTime
for users who are interested in weekend cooking projects.
Recipe Sorting
Sort recipes by total_time
to show users options that fit their time constraints.
Cooking Time Ranges
Combine these calculations with $avg
to give users a range of cooking times across your collection.
Advanced Tip: Adding Filters for Specific Recipe Types
What if you only want the minimum and maximum cooking times for a specific type of recipe, like “Dinner” recipes? You can add a $match
stage before $group
to filter the documents first:
> db.recipes.aggregate([
{ $match: { type: "Dinner" } },
{
$group: {
_id: null,
minCookTime: { $min: "$cook_time" },
maxCookTime: { $max: "$cook_time" },
},
},
])
This pipeline first filters documents where type
is "Dinner"
and then calculates the minimum and maximum cooking times within that subset.
Conclusion
MongoDB’s $min
and $max
operators are powerful tools for quickly finding the smallest and largest values in a dataset. In a recipe application, they help you analyze cooking times, create specialized filters, and provide users with useful insights into recipe duration.
By combining $min
, $max
, and other operators like $match
, you can build dynamic features that make your recipe collection more accessible and informative.
Experiment with $min
and $max
in your own MongoDB collections, and watch how these simple aggregations can enhance your app’s functionality and user experience!