Introduction
MongoDB's Aggregation Framework is a powerful tool for data processing and analysis. Unlike simple queries, aggregation pipelines enable complex data transformations, grouping, and analytics operations. This guide covers advanced aggregation patterns for production environments in 2025.
Aggregation Pipeline Basics
Pipeline Structure
Aggregation operations process documents through a pipeline of stages:
// Basic pipeline structure
db.collection.aggregate([
{ $match: { status: 'active' } }, // Stage 1: Filter
{ $group: { _id: '$category' } }, // Stage 2: Group
{ $sort: { count: -1 } } // Stage 3: Sort
]);Basic Aggregation Example
// Sales analytics
db.orders.aggregate([
// Stage 1: Filter recent orders
{
$match: {
createdAt: {
$gte: ISODate('2025-01-01')
}
}
},
// Stage 2: Group by user
{
$group: {
_id: '$userId',
totalOrders: { $sum: 1 },
totalSpent: { $sum: '$total' },
avgOrderValue: { $avg: '$total' }
}
},
// Stage 3: Sort by total spent
{
$sort: { totalSpent: -1 }
},
// Stage 4: Limit to top 10
{
$limit: 10
}
]);Core Aggregation Stages
$match
Filter documents early in the pipeline:
// Filter active users
db.users.aggregate([
{
$match: {
status: 'active',
age: { $gte: 18 }
}
}
]);$group
Group documents by a specified expression:
// Group products by category
db.products.aggregate([
{
$group: {
_id: '$category',
count: { $sum: 1 },
avgPrice: { $avg: '$price' },
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' },
products: {
$push: {
name: '$name',
price: '$price'
}
}
}
}
]);$project
Reshape documents in the pipeline:
db.users.aggregate([
{
$project: {
name: 1,
email: 1,
fullName: {
$concat: ['$firstName', ' ', '$lastName']
},
age: {
$subtract: [
{ $year: new Date() },
{ $year: '$birthDate' }
]
},
isActive: {
$cond: [
{ $eq: ['$status', 'active'] },
true,
false
]
}
}
}
]);Advanced Aggregation Patterns
Lookup (Left Outer Join)
Join collections with $lookup:
// Join orders with users
db.orders.aggregate([
{
$lookup: {
from: 'users',
localField: 'userId',
foreignField: '_id',
as: 'user'
}
},
{
$unwind: '$user' // Unwind array
},
{
$project: {
orderId: '$_id',
userName: '$user.name',
userEmail: '$user.email',
total: 1
}
}
]);Unwind
Deconstruct arrays:
// Flatten order items
db.orders.aggregate([
{
$unwind: '$items'
},
{
$group: {
_id: '$items.productId',
totalQuantity: { $sum: '$items.quantity' },
totalRevenue: {
$sum: {
$multiply: ['$items.quantity', '$items.price']
}
}
}
}
]);Faceted Search
Multiple aggregations in one query:
db.products.aggregate([
{
$match: {
category: 'electronics',
price: { $gte: 100, $lte: 1000 }
}
},
{
$facet: {
// Pagination
products: [
{ $skip: 0 },
{ $limit: 20 }
],
// Total count
totalCount: [
{ $count: 'count' }
],
// Price range distribution
priceRanges: [
{
$bucket: {
groupBy: '$price',
boundaries: [0, 200, 400, 600, 800, 1000],
default: '1000+',
output: {
count: { $sum: 1 },
products: { $push: '$name' }
}
}
}
],
// Average price by brand
avgPriceByBrand: [
{
$group: {
_id: '$brand',
avgPrice: { $avg: '$price' },
count: { $sum: 1 }
}
}
]
}
}
]);Data Transformation
Array Operators
// Filter array elements
db.users.aggregate([
{
$project: {
name: 1,
activeOrders: {
$filter: {
input: '$orders',
as: 'order',
cond: {
$eq: ['$$order.status', 'active']
}
}
}
}
}
]);
// Map array elements
db.orders.aggregate([
{
$project: {
items: {
$map: {
input: '$items',
as: 'item',
in: {
productName: '$$item.name',
total: {
$multiply: ['$$item.price', '$$item.quantity']
}
}
}
}
}
}
]);
// Reduce array
db.orders.aggregate([
{
$project: {
total: {
$reduce: {
input: '$items',
initialValue: 0,
in: {
$add: [
'$$value',
{ $multiply: ['$$this.price', '$$this.quantity'] }
]
}
}
}
}
}
]);Date Operations
// Sales by day
db.orders.aggregate([
{
$group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
totalSales: { $sum: '$total' },
count: { $sum: 1 }
}
},
{
$sort: {
'_id.year': 1,
'_id.month': 1,
'_id.day': 1
}
}
]);
// Time-based analytics
db.events.aggregate([
{
$project: {
event: 1,
hourOfDay: { $hour: '$timestamp' },
dayOfWeek: { $dayOfWeek: '$timestamp' },
week: { $week: '$timestamp' }
}
},
{
$group: {
_id: '$hourOfDay',
eventCount: { $sum: 1 }
}
}
]);Conditional Logic
// Conditional aggregation
db.orders.aggregate([
{
$project: {
total: 1,
discount: {
$cond: {
if: { $gt: ['$total', 100] },
then: { $multiply: ['$total', 0.1] },
else: 0
}
},
tier: {
$switch: {
branches: [
{ case: { $gte: ['$total', 500] }, then: 'platinum' },
{ case: { $gte: ['$total', 200] }, then: 'gold' },
{ case: { $gte: ['$total', 100] }, then: 'silver' }
],
default: 'bronze'
}
}
}
}
]);Performance Optimization
Pipeline Optimization
// Early filtering
db.orders.aggregate([
// Put $match early to reduce documents
{
$match: {
status: 'completed',
createdAt: { $gte: ISODate('2025-01-01') }
}
},
// Then project only needed fields
{
$project: {
userId: 1,
total: 1,
items: 1
}
},
// Finally group
{
$group: {
_id: '$userId',
totalSpent: { $sum: '$total' }
}
}
]);Index Usage
// Ensure aggregation uses indexes
db.orders.createIndex({ status: 1, createdAt: 1 });
// Use $match with indexed fields
db.orders.aggregate([
{
$match: {
status: 'completed',
createdAt: { $gte: ISODate('2025-01-01') }
}
}
]);Memory Limits
// Allow disk usage for large datasets
db.orders.aggregate(
[
{ $group: { _id: null, total: { $sum: '$total' } } }
],
{ allowDiskUse: true }
);Real-World Examples
E-commerce Analytics
// Complete sales dashboard
db.orders.aggregate([
// Filter date range
{
$match: {
createdAt: {
$gte: ISODate('2025-01-01'),
$lt: ISODate('2025-02-01')
}
}
},
// Unwind items
{ $unwind: '$items' },
// Join with products
{
$lookup: {
from: 'products',
localField: 'items.productId',
foreignField: '_id',
as: 'product'
}
},
{ $unwind: '$product' },
// Calculate metrics
{
$group: {
_id: {
category: '$product.category',
date: {
$dateToString: { format: '%Y-%m-%d', date: '$createdAt' }
}
},
revenue: {
$sum: {
$multiply: ['$items.quantity', '$items.price']
}
},
quantity: { $sum: '$items.quantity' },
orders: { $sum: 1 }
}
},
// Calculate growth rate
{
$group: {
_id: '$_id.category',
dailyStats: {
$push: {
date: '$_id.date',
revenue: '$revenue',
quantity: '$quantity'
}
},
totalRevenue: { $sum: '$revenue' },
avgDailyRevenue: { $avg: '$revenue' }
}
}
]);User Behavior Analysis
// User engagement analytics
db.events.aggregate([
// Filter specific events
{
$match: {
type: { $in: ['page_view', 'click', 'purchase'] }
}
},
// Group by user and session
{
$group: {
_id: {
userId: '$userId',
sessionId: '$sessionId'
},
events: { $push: '$$ROOT' },
firstEvent: { $min: '$timestamp' },
lastEvent: { $max: '$timestamp' }
}
},
// Calculate session metrics
{
$project: {
userId: '$_id.userId',
duration: {
$subtract: ['$lastEvent', '$firstEvent']
},
eventCount: { $size: '$events' },
eventTypes: {
$setUnion: ['$events.type']
}
}
},
// Group by user
{
$group: {
_id: '$userId',
totalSessions: { $sum: 1 },
avgSessionDuration: { $avg: '$duration' },
totalEvents: { $sum: '$eventCount' },
avgEventsPerSession: { $avg: '$eventCount' }
}
}
]);Time Series Analysis
// Moving average calculation
db.sales.aggregate([
// Sort by date
{ $sort: { date: 1 } },
// Calculate moving average
{
$setWindowFields: {
sortBy: { date: 1 },
output: {
movingAvg7Days: {
$avg: '$amount',
window: {
range: [-6, 0],
unit: 'day'
}
},
movingAvg30Days: {
$avg: '$amount',
window: {
range: [-29, 0],
unit: 'day'
}
}
}
}
}
]);Geo-Spatial Aggregation
// Location-based analytics
db.users.aggregate([
// Near specific location
{
$geoNear: {
near: {
type: 'Point',
coordinates: [-73.935242, 40.730610]
},
distanceField: 'distance',
maxDistance: 10000, // 10km
spherical: true
}
},
// Group by distance ranges
{
$bucket: {
groupBy: '$distance',
boundaries: [0, 1000, 5000, 10000],
default: '10000+',
output: {
count: { $sum: 1 },
users: { $push: '$name' }
}
}
}
]);Advanced Techniques
Graph Lookup
Recursive relationship traversal:
// Find all descendants
db.categories.aggregate([
{
$graphLookup: {
from: 'categories',
startWith: '$_id',
connectFromField: '_id',
connectToField: 'parentId',
as: 'descendants'
}
}
]);Redact
Conditional document filtering:
db.documents.aggregate([
{
$redact: {
$cond: {
if: { $eq: ['$accessLevel', 'public'] },
then: '$$KEEP',
else: {
$cond: {
if: { $eq: ['$accessLevel', 'private'] },
then: '$$PRUNE',
else: '$$DESCEND'
}
}
}
}
}
]);Best Practices
1. Filter Early
Always use $match as early as possible to reduce the dataset.
2. Project Needed Fields
Use $project to reduce document size before expensive operations.
3. Use Indexes
Ensure $match stages use indexed fields.
4. Monitor Performance
Use explain() to analyze pipeline performance:
db.orders.aggregate(
[
{ $group: { _id: '$userId', total: { $sum: '$total' } } }
],
{ explain: true }
);5. Limit Memory Usage
Use allowDiskUse for large datasets:
db.collection.aggregate(pipeline, { allowDiskUse: true });Conclusion
MongoDB's Aggregation Framework provides powerful data transformation and analytics capabilities. By mastering these advanced patterns, you can perform complex data analysis directly in the database, reducing application complexity and improving performance.