✏ MongoDB Aggregation: $sort, $count, $limit, $out stages
Today I'm wrapping up my short series about the Mongo aggregation framework, to cover a few additional stages that are often used at the end of the pipeline. Most are quite straightforward and might already be known, because they have a corresponding shell method (.find({}).sort()
, or .find({}).limit()
).
I'll stick to my Recipe collection for all examples below.
✏ $sort
I had shown yesterday how to retrieve a list of all ingredients from the recipe collection with a combination of $unwind
and $group
:
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } }
]);
// result
[
{ _id: 'tomatos', occurences: 3 },
{ _id: 'spaghetti', occurences: 1 },
{ _id: 'oil', occurences: 1 },
{ _id: 'pepper', occurences: 2 },
{ _id: 'salt', occurences: 2 },
{ _id: 'pesto', occurences: 1 }
]
To sort this list by name, add a $sort
stage and pass an object specifiying the field and a value (1
for ascending, -1
for descending):
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } },
{ $sort: { _id: 1 } }
]);
// result
[
{ _id: 'oil', occurences: 1 },
{ _id: 'pepper', occurences: 2 },
{ _id: 'pesto', occurences: 1 },
{ _id: 'salt', occurences: 2 },
{ _id: 'spaghetti', occurences: 1 },
{ _id: 'tomatos', occurences: 3 }
]
To sort by the occurences (to get the most "popular" ingredient):
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } },
{ $sort: { occurences: -1 } }
]);
// result
[
{ _id: 'tomatos', occurences: 3 },
{ _id: 'salt', occurences: 2 },
{ _id: 'pepper', occurences: 2 },
{ _id: 'pesto', occurences: 1 },
{ _id: 'spaghetti', occurences: 1 },
{ _id: 'oil', occurences: 1 }
]
In the result above, the ingredients are sorted by occurence, but unsorted within groups with the same number. To change that, you can add more field/value pairs to $sort
:
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } },
{ $sort: { occurences: -1, _id: 1 } }
]);
// result
[
{ _id: 'tomatos', occurences: 3 },
{ _id: 'pepper', occurences: 2 },
{ _id: 'salt', occurences: 2 },
{ _id: 'oil', occurences: 1 },
{ _id: 'pesto', occurences: 1 },
{ _id: 'spaghetti', occurences: 1 }
]
✏ $count
This stage returns the total number of result documents. The syntax is simple, you only pass a descriptive string of your choice:
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name' } },
{ $count: 'total of distinct ingredients' }
]);
// result
[ { 'total of distinct ingredients': 6 } ]
$count
can only be used at the end of the pipeline. If you want to count occurences of specific fields, use $group
with the $sum: 1
accumulator like above.
✏ $limit
This one is even more self-explanatory. Example how to get the Top 3 of ingredients:
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } },
{ $sort: { occurences: -1, _id: 1 } },
{ $limit: 3 }
]);
// result
[
{ _id: 'tomatos', occurences: 3 },
{ _id: 'pepper', occurences: 2 },
{ _id: 'salt', occurences: 2 }
]
✏ $out
This stage writes the result of the aggregation into a new collection in the database:
Recipe.aggregate([
{ $unwind: '$ingredients' },
{ $group: { _id: '$ingredients.name', occurences: { $sum: 1 } } },
{ $out: 'resultcollection' }
]);
This can be useful if you're dealing with large amounts of data, and need to perform a very expensive aggregation. If it costs too much memory to do all stages at once, you can split up the aggregation and (temporarily) store an intermediate result.
✏ Recap
This post covered:
$sort stage
$count stage
$limit stage
$out stage
✏ Resources
✏ Thanks for reading!
I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subscribe to my newsletter. Until next time 👋
✏ Previous Posts
You can find an overview of all previous posts with tags and tag search here: