MongoDB $sort Stage
Introduction
The $sort
stage is one of the most frequently used stages in MongoDB's Aggregation Framework. It allows you to control the order of documents as they flow through your aggregation pipeline. Whether you're building reports, displaying data in user interfaces, or preparing data for further processing, sorting is often a crucial step in your data manipulation process.
In this tutorial, we'll dive deep into the $sort
stage, explaining its syntax, usage patterns, and providing practical examples to help you master document sorting in MongoDB.
Understanding the $sort Stage
The $sort
stage orders the documents in the aggregation pipeline based on the specified field(s). You can sort documents in either ascending (1) or descending (-1) order.
Basic Syntax
{
$sort: {
<field1>: <sort order>,
<field2>: <sort order>,
...
}
}
Where:
<field>
is the field name you want to sort by<sort order>
is either:1
for ascending order (smallest to largest, A to Z)-1
for descending order (largest to smallest, Z to A)
Basic Usage Examples
Let's start with a simple collection of products to demonstrate how $sort
works:
// Sample products collection
[
{ _id: 1, name: "Laptop", price: 1200, category: "Electronics" },
{ _id: 2, name: "Headphones", price: 150, category: "Electronics" },
{ _id: 3, name: "Coffee Mug", price: 15, category: "Kitchen" },
{ _id: 4, name: "Monitor", price: 300, category: "Electronics" },
{ _id: 5, name: "Desk Chair", price: 250, category: "Furniture" }
]
Example 1: Sort by Price (Ascending)
db.products.aggregate([
{ $sort: { price: 1 } }
])
Output:
[
{ _id: 3, name: "Coffee Mug", price: 15, category: "Kitchen" },
{ _id: 2, name: "Headphones", price: 150, category: "Electronics" },
{ _id: 5, name: "Desk Chair", price: 250, category: "Furniture" },
{ _id: 4, name: "Monitor", price: 300, category: "Electronics" },
{ _id: 1, name: "Laptop", price: 1200, category: "Electronics" }
]
Example 2: Sort by Price (Descending)
db.products.aggregate([
{ $sort: { price: -1 } }
])
Output:
[
{ _id: 1, name: "Laptop", price: 1200, category: "Electronics" },
{ _id: 4, name: "Monitor", price: 300, category: "Electronics" },
{ _id: 5, name: "Desk Chair", price: 250, category: "Furniture" },
{ _id: 2, name: "Headphones", price: 150, category: "Electronics" },
{ _id: 3, name: "Coffee Mug", price: 15, category: "Kitchen" }
]
Example 3: Sorting by Multiple Fields
You can sort by multiple fields to handle ties in your primary sort field:
db.products.aggregate([
{ $sort: { category: 1, price: -1 } }
])
Output:
[
{ _id: 1, name: "Laptop", price: 1200, category: "Electronics" },
{ _id: 4, name: "Monitor", price: 300, category: "Electronics" },
{ _id: 2, name: "Headphones", price: 150, category: "Electronics" },
{ _id: 5, name: "Desk Chair", price: 250, category: "Furniture" },
{ _id: 3, name: "Coffee Mug", price: 15, category: "Kitchen" }
]
This example sorts documents first by category (alphabetically) and then by price (highest to lowest) within each category.
Advanced Usage and Tips
Sort Position in the Pipeline
The position of the $sort
stage in your aggregation pipeline is important for both performance and functionality:
-
Early sorting: Place
$sort
early in the pipeline (after$match
if possible) to:- Utilize indexes for better performance
- Reduce the number of documents to sort if used after
$match
-
Late sorting: Place
$sort
later in the pipeline when:- You need to sort on calculated fields
- You want to sort on the results of previous transformations
Using limit
A common and efficient pattern is combining $sort
with $limit
to get the "top N" documents:
// Get the 3 most expensive products
db.products.aggregate([
{ $sort: { price: -1 } },
{ $limit: 3 }
])
Output:
[
{ _id: 1, name: "Laptop", price: 1200, category: "Electronics" },
{ _id: 4, name: "Monitor", price: 300, category: "Electronics" },
{ _id: 5, name: "Desk Chair", price: 250, category: "Furniture" }
]
Memory Restrictions
By default, $sort
has a 100MB memory limit. For sorting larger datasets:
- Use the
allowDiskUse: true
option to enable disk-based sorting:
db.products.aggregate([
{ $sort: { price: 1 } }
],
{ allowDiskUse: true })
- Use indexing strategies for optimal performance:
// Create an index on the price field
db.products.createIndex({ price: 1 })
Real-World Applications
Example 1: E-commerce Product Listing
Creating a paginated product list with sorting options:
// Parameters
const sortField = "price";
const sortOrder = -1; // descending
const pageSize = 10;
const pageNumber = 2;
const skip = (pageNumber - 1) * pageSize;
// Aggregation
db.products.aggregate([
{ $match: { inStock: true } },
{ $sort: { [sortField]: sortOrder } },
{ $skip: skip },
{ $limit: pageSize },
{
$project: {
name: 1,
price: 1,
category: 1,
rating: 1,
thumbnail: 1
}
}
])
This pipeline:
- Filters for in-stock products
- Sorts by price (highest first)
- Implements pagination
- Returns only the necessary fields for display
Example 2: Sales Reporting
Creating a report of top-selling products by revenue:
db.orders.aggregate([
{ $match: { orderDate: { $gte: new Date("2023-01-01"), $lt: new Date("2024-01-01") } } },
{ $unwind: "$items" },
{
$group: {
_id: "$items.productId",
productName: { $first: "$items.name" },
totalRevenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
unitsSold: { $sum: "$items.quantity" }
}
},
{ $sort: { totalRevenue: -1 } },
{ $limit: 10 },
{
$project: {
_id: 0,
productId: "$_id",
productName: 1,
totalRevenue: { $round: ["$totalRevenue", 2] },
unitsSold: 1
}
}
])
This aggregation:
- Filters orders from 2023
- Breaks down orders into individual items
- Groups and calculates revenue by product
- Sorts by total revenue (highest first)
- Gets only the top 10 products
- Formats the output for reporting
Performance Considerations
When using the $sort
stage, keep these performance tips in mind:
-
Use indexes for sorting whenever possible
- Create indexes on frequently sorted fields
- Compound indexes should match your sort patterns
-
Limit documents before sorting
- Use
$match
before$sort
to reduce the dataset - Filter data as early as possible in the pipeline
- Use
-
Memory usage awareness
$sort
requires memory to hold all documents for sorting- Use
allowDiskUse: true
for large datasets - Monitor your MongoDB instance during heavy aggregations
-
Avoid sorting unnecessarily
- Only sort when order matters for your application logic
- Consider if sorting can happen on the client side for small datasets
Common Challenges and Solutions
Sorting on Embedded Fields
To sort on fields inside embedded documents, use the dot notation:
// Sample user collection with embedded address
db.users.aggregate([
{ $sort: { "address.city": 1, "address.zipcode": 1 } }
])
Sorting by Computed Values
If you need to sort by a computed value, create it first with $addFields
or $project
:
db.products.aggregate([
{
$addFields: {
discountedPrice: { $subtract: ["$price", "$discount"] }
}
},
{ $sort: { discountedPrice: 1 } }
])
Sorting with Null Values
MongoDB places null values before non-null values in ascending sorts. If you need different behavior:
// Put null values last in ascending sort
db.customers.aggregate([
{
$sort: {
// If lastPurchaseDate is null, use a far future date, else use the actual date
lastPurchaseDate: 1
}
}
])
// Alternative approach using $addFields
db.customers.aggregate([
{
$addFields: {
sortField: {
$cond: {
if: { $eq: ["$lastPurchaseDate", null] },
then: new Date("9999-12-31"),
else: "$lastPurchaseDate"
}
}
}
},
{ $sort: { sortField: 1 } },
{ $project: { sortField: 0 } } // Remove the temporary field
])
Summary
The $sort
stage is a fundamental tool in MongoDB's aggregation framework that allows you to control the order of documents in your results. Key takeaways from this tutorial:
- Use
1
for ascending order and-1
for descending order - You can sort on multiple fields for more complex ordering
- Position the
$sort
stage strategically in your pipeline for optimal performance - Combine
$sort
with$limit
for efficient "top N" queries - Use
allowDiskUse: true
for sorting large datasets - Leverage indexes to improve sorting performance
Mastering the $sort
stage will help you create more organized and useful results in your MongoDB aggregations, whether for reporting, data analysis, or application data display.
Exercises
To practice what you've learned, try these exercises:
-
Create a collection of at least 10 user documents with fields for name, age, registration date, and subscription tier. Write an aggregation that:
- Sorts users by subscription tier (ascending) and age (descending)
- Limits the result to the first 5 documents
-
Using a collection of movie documents with fields for title, release year, genres (array), and rating:
- Sort movies by rating (descending) and release year (descending)
- Group the results by genre and get the top rated movie for each genre
-
Using the products collection from the examples:
- Add a computed field that represents a "value score" (price divided by rating)
- Sort products by this value score to identify the best value products
Additional Resources
- MongoDB Official Documentation: $sort
- MongoDB Performance Best Practices
- MongoDB Indexing Strategies
Happy aggregating!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)