#100DaysOfMERN - Day 52

#100DaysOfMERN - Day 52

·

4 min read

✏ 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 $sortstage 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

MongoDB Aggregation Framework


✏ 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:

#100DaysOfMERN - The App