MongoDB Aggregation Pipeline for Complex Analytics

Introduction
MongoDB's aggregation pipeline represents one of the most powerful features for data analysis and transformation within the database itself. Unlike simple find operations that retrieve documents as-is, the aggregation framework enables sophisticated data processing, complex calculations, and multi-dimensional analysis that can transform raw data into actionable business insights.
The aggregation pipeline processes documents through a sequence of stages, each transforming the data stream to produce increasingly refined results. This approach enables complex analytics previously requiring external tools or multiple query rounds, while leveraging MongoDB's distributed architecture for optimal performance across large datasets.
For organizations seeking to extract maximum value from their MongoDB data, mastering the aggregation pipeline is essential. Whether you're building real-time dashboards, generating complex reports, or performing advanced analytics for machine learning, the aggregation framework provides the foundation for sophisticated data processing workflows.
This comprehensive guide explores advanced aggregation techniques, optimization strategies, and real-world implementation patterns that enable complex analytics directly within MongoDB. You'll learn to design efficient pipelines, optimize performance for large datasets, and implement analytics solutions that scale with your data growth.
Table of Contents
- Aggregation Pipeline Fundamentals
- Advanced Pipeline Stages
- Complex Analytics Patterns
- Performance Optimization Techniques
- Real-World Analytics Examples
- Window Functions and Time Series
- Data Transformation and ETL
- Pipeline Optimization Strategies
- Integration with BI Tools
- Best Practices and Guidelines
- FAQ Section
Aggregation Pipeline Fundamentals
Understanding the aggregation pipeline's conceptual model and execution characteristics forms the foundation for building complex analytics solutions.
Pipeline Execution Model
Sequential Stage Processing: The aggregation pipeline processes documents through sequential stages, each receiving the output of the previous stage:
// Conceptual pipeline flow
Input Documents → Stage 1 → Stage 2 → Stage 3 → Final Results
// Example: Sales analysis pipeline
db.orders.aggregate([
// Stage 1: Filter to current year
{$match: {
orderDate: {
$gte: ISODate("2025-01-01"),
$lt: ISODate("2026-01-01")
}
}},
// Stage 2: Unwind order items
{$unwind: "$items"},
// Stage 3: Group by product category
{$group: {
_id: "$items.category",
totalRevenue: {$sum: {$multiply: ["$items.quantity", "$items.price"]}},
totalOrders: {$sum: 1},
avgOrderValue: {$avg: {$multiply: ["$items.quantity", "$items.price"]}}
}},
// Stage 4: Sort by revenue descending
{$sort: {totalRevenue: -1}},
// Stage 5: Add computed fields
{$addFields: {
revenuePercentage: {
$multiply: [
{$divide: ["$totalRevenue", {$sum: "$totalRevenue"}]},
100
]
}
}}
]);Core Pipeline Stages
Essential Stages for Analytics:
// $match: Filtering documents (similar to WHERE clause)
{$match: {
status: "completed",
total: {$gte: 100},
customerType: {$in: ["premium", "enterprise"]}
}}
// $group: Aggregating data (similar to GROUP BY)
{$group: {
_id: {
year: {$year: "$orderDate"},
month: {$month: "$orderDate"}
},
totalSales: {$sum: "$total"},
orderCount: {$sum: 1},
uniqueCustomers: {$addToSet: "$customerId"},
avgOrderValue: {$avg: "$total"},
maxOrderValue: {$max: "$total"},
minOrderValue: {$min: "$total"}
}}
// $project: Reshaping documents (similar to SELECT clause)
{$project: {
customerName: 1,
orderTotal: "$total",
year: {$year: "$orderDate"},
monthName: {
$switch: {
branches: [
{case: {$eq: [{$month: "$orderDate"}, 1]}, then: "January"},
{case: {$eq: [{$month: "$orderDate"}, 2]}, then: "February"},
// ... other months
],
default: "Unknown"
}
},
isLargeOrder: {$gte: ["$total", 500]}
}}
// $sort: Ordering results
{$sort: {
totalSales: -1, // Descending by sales
orderCount: -1 // Then by order count
}}
// $limit and $skip: Pagination
{$skip: 20},
{$limit: 10} // Get records 21-30Data Types and Expressions
Working with Different Data Types:
// Complex expression examples
db.products.aggregate([
{$project: {
name: 1,
price: 1,
// String operations
productCode: {$toUpper: "$sku"},
description: {$substr: ["$description", 0, 100]},
// Date operations
yearLaunched: {$year: "$launchDate"},
daysSinceLaunch: {
$divide: [
{$subtract: [new Date(), "$launchDate"]},
1000 * 60 * 60 * 24 // Convert milliseconds to days
]
},
// Conditional logic
priceCategory: {
$switch: {
branches: [
{case: {$lt: ["$price", 50]}, then: "Budget"},
{case: {$lt: ["$price", 200]}, then: "Mid-range"},
{case: {$lt: ["$price", 500]}, then: "Premium"}
],
default: "Luxury"
}
},
// Array operations
tagCount: {$size: "$tags"},
hasElectronicsTag: {$in: ["electronics", "$tags"]},
// Mathematical operations
priceAfterDiscount: {
$multiply: [
"$price",
{$subtract: [1, {$divide: ["$discountPercentage", 100]}]}
]
}
}}
]);Advanced Pipeline Stages
Sophisticated analytics require mastery of advanced pipeline stages that enable complex data transformations and analysis patterns.
$lookup: Joining Collections
Left Outer Joins with Related Data:
// Basic lookup: Orders with customer information
db.orders.aggregate([
{$lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customerInfo"
}},
// Unwind to flatten the array (assuming one-to-one relationship)
{$unwind: "$customerInfo"},
{$project: {
orderNumber: 1,
total: 1,
customerName: "$customerInfo.name",
customerEmail: "$customerInfo.email",
customerTier: "$customerInfo.tier"
}}
]);
// Advanced lookup with pipeline (MongoDB 3.6+)
db.orders.aggregate([
{$lookup: {
from: "products",
let: {productIds: "$items.productId"},
pipeline: [
{$match: {
$expr: {$in: ["$_id", "$$productIds"]},
status: "active" // Additional filtering
}},
{$project: {name: 1, category: 1, price: 1}} // Select specific fields
],
as: "productDetails"
}}
]);
// Self-lookup for hierarchical data
db.employees.aggregate([
{$lookup: {
from: "employees",
localField: "managerId",
foreignField: "_id",
as: "manager"
}},
{$lookup: {
from: "employees",
localField: "_id",
foreignField: "managerId",
as: "directReports"
}},
{$project: {
name: 1,
department: 1,
managerName: {$arrayElemAt: ["$manager.name", 0]},
teamSize: {$size: "$directReports"}
}}
]);$facet: Multi-Dimensional Analysis
Parallel Analytics Pipelines:
// Multi-faceted product analysis
db.products.aggregate([
{$match: {status: "active"}},
{$facet: {
// Price distribution analysis
priceDistribution: [
{$bucket: {
groupBy: "$price",
boundaries: [0, 50, 100, 200, 500, 1000, Infinity],
default: "Other",
output: {
count: {$sum: 1},
avgPrice: {$avg: "$price"},
products: {$push: {name: "$name", price: "$price"}}
}
}}
],
// Category analysis
categoryStats: [
{$group: {
_id: "$category",
productCount: {$sum: 1},
avgPrice: {$avg: "$price"},
totalInventory: {$sum: "$inventory.quantity"}
}},
{$sort: {productCount: -1}}
],
// Top performers
topProducts: [
{$match: {"sales.monthlyRevenue": {$exists: true}}},
{$sort: {"sales.monthlyRevenue": -1}},
{$limit: 10},
{$project: {
name: 1,
category: 1,
monthlyRevenue: "$sales.monthlyRevenue",
unitsSold: "$sales.unitsSold"
}}
],
// Inventory alerts
lowStock: [
{$match: {
$expr: {$lt: ["$inventory.quantity", "$inventory.reorderPoint"]}
}},
{$project: {
name: 1,
currentStock: "$inventory.quantity",
reorderPoint: "$inventory.reorderPoint",
supplier: "$inventory.supplier"
}}
]
}}
]);$graphLookup: Graph Traversal
Hierarchical and Network Analysis:
// Organization hierarchy traversal
db.employees.aggregate([
{$match: {_id: ObjectId("manager_id")}},
{$graphLookup: {
from: "employees",
startWith: "$_id",
connectFromField: "_id",
connectToField: "managerId",
as: "allReports",
maxDepth: 10,
depthField: "level"
}},
{$project: {
managerName: "$name",
totalReports: {$size: "$allReports"},
directReports: {
$size: {
$filter: {
input: "$allReports",
cond: {$eq: ["$$this.level", 0]}
}
}
},
organizationDepth: {$max: "$allReports.level"}
}}
]);
// Social network friend recommendations
db.users.aggregate([
{$match: {_id: ObjectId("user_id")}},
// Find friends of friends
{$graphLookup: {
from: "users",
startWith: "$friends",
connectFromField: "friends",
connectToField: "_id",
as: "friendsOfFriends",
maxDepth: 1
}},
{$project: {
userName: "$name",
suggestions: {
$filter: {
input: "$friendsOfFriends",
cond: {
$and: [
{$ne: ["$$this._id", "$_id"]}, // Not the user themselves
{$not: {$in: ["$$this._id", "$friends"]}} // Not already a friend
]
}
}
}
}},
{$unwind: "$suggestions"},
{$group: {
_id: "$suggestions._id",
suggestedUser: {$first: "$suggestions.name"},
mutualFriendsCount: {$sum: 1}
}},
{$sort: {mutualFriendsCount: -1}},
{$limit: 10}
]);Complex Analytics Patterns
Real-world analytics often require sophisticated patterns that combine multiple stages and advanced techniques.
Time Series Analytics
Comprehensive Time-Based Analysis:
// Revenue trend analysis with moving averages
db.dailySales.aggregate([
{$match: {
date: {
$gte: ISODate("2024-01-01"),
$lt: ISODate("2025-01-01")
}
}},
{$sort: {date: 1}},
// Add window function for moving averages
{$setWindowFields: {
sortBy: {date: 1},
output: {
// 7-day moving average
movingAvg7: {
$avg: "$revenue",
window: {
documents: [-6, 0] // Current + 6 previous days
}
},
// 30-day moving average
movingAvg30: {
$avg: "$revenue",
window: {
documents: [-29, 0]
}
},
// Running total
runningTotal: {
$sum: "$revenue",
window: {
documents: ["unbounded", 0]
}
},
// Day-over-day change
previousDayRevenue: {
$first: "$revenue",
window: {
documents: [-1, -1]
}
}
}
}},
{$addFields: {
dayOverDayChange: {
$subtract: ["$revenue", "$previousDayRevenue"]
},
dayOverDayPercent: {
$multiply: [
{$divide: [
{$subtract: ["$revenue", "$previousDayRevenue"]},
"$previousDayRevenue"
]},
100
]
}
}},
// Monthly aggregation
{$group: {
_id: {
year: {$year: "$date"},
month: {$month: "$date"}
},
monthlyRevenue: {$sum: "$revenue"},
avgDailyRevenue: {$avg: "$revenue"},
avgMovingAvg7: {$avg: "$movingAvg7"},
avgMovingAvg30: {$avg: "$movingAvg30"},
daysWithGrowth: {
$sum: {$cond: [{$gt: ["$dayOverDayChange", 0]}, 1, 0]}
},
totalDays: {$sum: 1}
}},
{$addFields: {
growthRate: {
$divide: ["$daysWithGrowth", "$totalDays"]
}
}},
{$sort: {"_id.year": 1, "_id.month": 1}}
]);Cohort Analysis
Customer Retention and Behavior Analysis:
// Customer cohort analysis by registration month
db.orders.aggregate([
// Join with customer data to get registration date
{$lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customer"
}},
{$unwind: "$customer"},
// Calculate cohort month and order month
{$addFields: {
cohortMonth: {
$dateFromParts: {
year: {$year: "$customer.registrationDate"},
month: {$month: "$customer.registrationDate"}
}
},
orderMonth: {
$dateFromParts: {
year: {$year: "$orderDate"},
month: {$month: "$orderDate"}
}
}
}},
// Calculate months since registration
{$addFields: {
monthsFromRegistration: {
$divide: [
{$subtract: ["$orderMonth", "$cohortMonth"]},
1000 * 60 * 60 * 24 * 30 // Approximate months
]
}
}},
// Group by cohort and month
{$group: {
_id: {
cohortMonth: "$cohortMonth",
monthsFromRegistration: {$floor: "$monthsFromRegistration"}
},
uniqueCustomers: {$addToSet: "$customerId"},
totalRevenue: {$sum: "$total"},
orderCount: {$sum: 1}
}},
// Calculate cohort metrics
{$group: {
_id: "$_id.cohortMonth",
cohortData: {
$push: {
month: "$_id.monthsFromRegistration",
customers: {$size: "$uniqueCustomers"},
revenue: "$totalRevenue",
orders: "$orderCount"
}
}
}},
// Calculate retention rates
{$addFields: {
baselineCustomers: {
$arrayElemAt: [
{$filter: {
input: "$cohortData",
cond: {$eq: ["$$this.month", 0]}
}},
0
]
}
}},
{$addFields: {
cohortAnalysis: {
$map: {
input: "$cohortData",
as: "period",
in: {
month: "$$period.month",
customers: "$$period.customers",
retentionRate: {
$divide: [
"$$period.customers",
"$baselineCustomers.customers"
]
},
revenuePerCustomer: {
$divide: [
"$$period.revenue",
"$$period.customers"
]
}
}
}
}
}},
{$sort: {_id: 1}}
]);Advanced Statistical Analysis
Statistical Measures and Outlier Detection:
// Product performance statistical analysis
db.products.aggregate([
{$match: {status: "active"}},
// First pass: collect all revenue values
{$group: {
_id: null,
revenues: {$push: "$monthlyRevenue"},
totalProducts: {$sum: 1}
}},
// Calculate statistical measures
{$addFields: {
sortedRevenues: {$sortArray: {input: "$revenues", sortBy: 1}},
mean: {$avg: "$revenues"},
// Calculate median
median: {
$let: {
vars: {
sorted: {$sortArray: {input: "$revenues", sortBy: 1}},
length: {$size: "$revenues"}
},
in: {
$cond: {
if: {$eq: [{$mod: ["$$length", 2]}, 0]},
then: {
$avg: [
{$arrayElemAt: ["$$sorted", {$divide: [{$subtract: ["$$length", 1]}, 2]}]},
{$arrayElemAt: ["$$sorted", {$divide: ["$$length", 2]}]}
]
},
else: {
$arrayElemAt: ["$$sorted", {$floor: {$divide: ["$$length", 2]}}]
}
}
}
}
}
}},
// Calculate quartiles and IQR
{$addFields: {
q1: {
$arrayElemAt: [
"$sortedRevenues",
{$floor: {$multiply: [0.25, {$subtract: ["$totalProducts", 1]}]}}
]
},
q3: {
$arrayElemAt: [
"$sortedRevenues",
{$floor: {$multiply: [0.75, {$subtract: ["$totalProducts", 1]}]}}
]
}
}},
{$addFields: {
iqr: {$subtract: ["$q3", "$q1"]},
lowerFence: {$subtract: ["$q1", {$multiply: [1.5, {$subtract: ["$q3", "$q1"]}]}]},
upperFence: {$add: ["$q3", {$multiply: [1.5, {$subtract: ["$q3", "$q1"]}]}]}
}},
// Identify outliers
{$addFields: {
outliers: {
$filter: {
input: "$revenues",
cond: {
$or: [
{$lt: ["$$this", "$lowerFence"]},
{$gt: ["$$this", "$upperFence"]}
]
}
}
}
}},
{$project: {
totalProducts: 1,
mean: {$round: ["$mean", 2]},
median: {$round: ["$median", 2]},
q1: {$round: ["$q1", 2]},
q3: {$round: ["$q3", 2]},
iqr: {$round: ["$iqr", 2]},
outlierCount: {$size: "$outliers"},
outlierPercentage: {
$round: [
{$multiply: [
{$divide: [{$size: "$outliers"}, "$totalProducts"]},
100
]}, 2
]
}
}}
]);Performance Optimization Techniques
Optimizing aggregation pipelines for large datasets requires understanding execution patterns and applying targeted optimization strategies.
Index Utilization in Pipelines
Designing Indexes for Aggregation Performance:
// Optimize pipeline stages for index usage
// Stage order optimization: $match early
db.orders.aggregate([
// GOOD: $match first to use indexes
{$match: {
orderDate: {$gte: ISODate("2025-01-01")},
status: "completed",
"customer.tier": "premium"
}},
// Then transform data
{$unwind: "$items"},
{$group: {
_id: "$items.category",
revenue: {$sum: {$multiply: ["$items.quantity", "$items.price"]}}
}}
]);
// Supporting indexes for optimal performance
db.orders.createIndex({
orderDate: 1,
status: 1,
"customer.tier": 1
});
// Compound index design for multi-stage pipelines
db.orders.createIndex({
status: 1, // Most selective first
orderDate: -1, // Range query
"customer.tier": 1 // Additional filter
});
// Partial indexes for specific use cases
db.orders.createIndex(
{orderDate: -1, total: -1},
{
partialFilterExpression: {
status: "completed",
total: {$gte: 100}
}
}
);Memory Usage Optimization
Managing Memory-Intensive Operations:
// Memory-efficient pipeline design
db.sales.aggregate([
// Use $match to reduce dataset size early
{$match: {
date: {$gte: ISODate("2024-01-01")},
amount: {$gte: 50}
}},
// Project only necessary fields
{$project: {
date: 1,
amount: 1,
customerId: 1,
productId: 1
// Exclude large fields like descriptions, metadata
}},
// Use $sample for large datasets when exact results aren't needed
{$sample: {size: 10000}},
// Group operations - consider memory limits
{$group: {
_id: {
month: {$dateToString: {format: "%Y-%m", date: "$date"}},
product: "$productId"
},
totalSales: {$sum: "$amount"},
avgSale: {$avg: "$amount"},
// Avoid collecting large arrays that might exceed memory
// customerIds: {$addToSet: "$customerId"} // CAREFUL: can be memory-intensive
uniqueCustomers: {$sum: 1} // Use count instead of collecting IDs
}},
// Use $sort with $limit to reduce memory usage
{$sort: {totalSales: -1}},
{$limit: 100}
], {
allowDiskUse: true, // Enable for large datasets
maxTimeMS: 30000 // Set timeout for long-running aggregations
});
// Alternative approach for memory-intensive operations
db.sales.aggregate([
{$match: {date: {$gte: ISODate("2024-01-01")}}},
// Use $bucket for efficient grouping of large datasets
{$bucket: {
groupBy: "$amount",
boundaries: [0, 50, 100, 500, 1000, 5000, Infinity],
default: "Other",
output: {
count: {$sum: 1},
totalRevenue: {$sum: "$amount"},
avgRevenue: {$avg: "$amount"}
}
}}
]);Pipeline Stage Optimization
Optimizing Individual Stages:
// Efficient $lookup operations
db.orders.aggregate([
// Filter before lookup to reduce join size
{$match: {
orderDate: {$gte: ISODate("2025-01-01")},
status: "completed"
}},
// Optimized lookup with pipeline
{$lookup: {
from: "customers",
let: {customerId: "$customerId"},
pipeline: [
{$match: {
$expr: {$eq: ["$_id", "$$customerId"]},
status: "active" // Additional filtering in lookup
}},
{$project: {name: 1, tier: 1, email: 1}} // Project only needed fields
],
as: "customer"
}},
// Unwind efficiently
{$unwind: {
path: "$customer",
preserveNullAndEmptyArrays: false // Exclude documents without matches
}},
// Continue with analysis
{$group: {
_id: "$customer.tier",
totalOrders: {$sum: 1},
totalRevenue: {$sum: "$total"}
}}
]);
// Efficient text search in aggregation
db.products.aggregate([
// Use $match with text search early
{$match: {
$text: {$search: "wireless bluetooth headphones"},
status: "active"
}},
// Add relevance score
{$addFields: {
score: {$meta: "textScore"}
}},
// Sort by relevance
{$sort: {score: {$meta: "textScore"}}},
// Limit results early
{$limit: 100},
// Then perform additional processing
{$lookup: {
from: "reviews",
localField: "_id",
foreignField: "productId",
as: "reviews"
}}
]);Real-World Analytics Examples
E-commerce Revenue Dashboard
Comprehensive Sales Analytics Pipeline:
// E-commerce dashboard aggregation
db.orders.aggregate([
{$match: {
orderDate: {
$gte: ISODate("2024-01-01"),
$lt: ISODate("2025-01-01")
},
status: {$in: ["completed", "shipped", "delivered"]}
}},
{$facet: {
// Revenue trends by month
monthlyTrends: [
{$group: {
_id: {
year: {$year: "$orderDate"},
month: {$month: "$orderDate"}
},
revenue: {$sum: "$total"},
orders: {$sum: 1},
uniqueCustomers: {$addToSet: "$customerId"}
}},
{$addFields: {
avgOrderValue: {$divide: ["$revenue", "$orders"]},
uniqueCustomerCount: {$size: "$uniqueCustomers"}
}},
{$sort: {"_id.year": 1, "_id.month": 1}},
// Add month-over-month growth
{$setWindowFields: {
sortBy: {"_id.year": 1, "_id.month": 1},
output: {
previousMonthRevenue: {
$shift: {
output: "$revenue",
by: -1
}
}
}
}},
{$addFields: {
monthOverMonthGrowth: {
$cond: {
if: {$gt: ["$previousMonthRevenue", 0]},
then: {
$multiply: [
{$divide: [
{$subtract: ["$revenue", "$previousMonthRevenue"]},
"$previousMonthRevenue"
]},
100
]
},
else: null
}
}
}}
],
// Customer segmentation
customerSegments: [
{$group: {
_id: "$customerId",
totalSpent: {$sum: "$total"},
orderCount: {$sum: 1},
firstOrder: {$min: "$orderDate"},
lastOrder: {$max: "$orderDate"}
}},
{$addFields: {
avgOrderValue: {$divide: ["$totalSpent", "$orderCount"]},
segment: {
$switch: {
branches: [
{
case: {$and: [
{$gte: ["$totalSpent", 1000]},
{$gte: ["$orderCount", 10]}
]},
then: "VIP"
},
{
case: {$and: [
{$gte: ["$totalSpent", 500]},
{$gte: ["$orderCount", 5]}
]},
then: "Loyal"
},
{
case: {$gte: ["$totalSpent", 100]},
then: "Regular"
}
],
default: "New"
}
}
}},
{$group: {
_id: "$segment",
customerCount: {$sum: 1},
totalRevenue: {$sum: "$totalSpent"},
avgLifetimeValue: {$avg: "$totalSpent"}
}}
]
}}
]);Marketing Campaign Analytics
Campaign Performance and Attribution Analysis:
// Marketing campaign effectiveness analysis
db.events.aggregate([
{$match: {
timestamp: {
$gte: ISODate("2024-01-01"),
$lt: ISODate("2025-01-01")
},
eventType: {$in: ["campaign_click", "page_view", "purchase"]}
}},
// Create customer journey tracking
{$sort: {userId: 1, timestamp: 1}},
{$group: {
_id: "$userId",
events: {
$push: {
type: "$eventType",
timestamp: "$timestamp",
campaignId: "$campaignId",
source: "$source",
medium: "$medium",
amount: "$amount"
}
}
}},
// Analyze conversion paths
{$addFields: {
firstTouch: {$arrayElemAt: ["$events", 0]},
lastTouch: {$arrayElemAt: ["$events", -1]},
// Find first campaign interaction
firstCampaignTouch: {
$arrayElemAt: [
{$filter: {
input: "$events",
cond: {$ne: ["$$this.campaignId", null]}
}},
0
]
},
// Calculate conversion
hasConverted: {
$gt: [{
$size: {
$filter: {
input: "$events",
cond: {$eq: ["$$this.type", "purchase"]}
}
}
}, 0]
}
}},
// Attribution analysis
{$group: {
_id: {
firstTouchCampaign: "$firstCampaignTouch.campaignId",
source: "$firstCampaignTouch.source"
},
totalUsers: {$sum: 1},
conversions: {$sum: {$cond: ["$hasConverted", 1, 0]}},
totalRevenue: {$sum: "$totalRevenue"}
}},
{$addFields: {
conversionRate: {
$multiply: [
{$divide: ["$conversions", "$totalUsers"]},
100
]
}
}},
{$sort: {totalRevenue: -1}}
]);Window Functions and Time Series
MongoDB's window functions enable sophisticated time-based analytics and comparative analysis.
Time Series Window Operations
Advanced Time-Based Analysis:
// Stock price analysis with technical indicators
db.stockPrices.aggregate([
{$match: {
symbol: "AAPL",
date: {$gte: ISODate("2024-01-01")}
}},
{$sort: {date: 1}},
{$setWindowFields: {
partitionBy: "$symbol",
sortBy: {date: 1},
output: {
// Simple Moving Averages
sma_5: {
$avg: "$close",
window: {documents: [-4, 0]} // 5-day SMA
},
sma_20: {
$avg: "$close",
window: {documents: [-19, 0]} // 20-day SMA
},
// Price momentum
priceChange_1d: {
$subtract: [
"$close",
{$first: "$close", window: {documents: [-1, -1]}}
]
},
priceChange_5d: {
$subtract: [
"$close",
{$first: "$close", window: {documents: [-5, -5]}}
]
},
// Volatility (standard deviation)
volatility_20d: {
$stdDevPop: "$close",
window: {documents: [-19, 0]}
},
// Support and resistance levels
highest_52w: {
$max: "$close",
window: {documents: [-251, 0]} // Approximately 252 trading days in a year
},
lowest_52w: {
$min: "$close",
window: {documents: [-251, 0]}
}
}
}},
// Add technical indicators
{$addFields: {
// Golden Cross (bullish signal)
goldenCross: {$gt: ["$sma_5", "$sma_20"]},
// Above/below moving averages
aboveSMA20: {$gt: ["$close", "$sma_20"]},
// Volume spike
volumeSpike: {
$gt: ["$volume", {$multiply: ["$avgVolume_20d", 1.5]}]
},
// Price near 52-week high/low
nearHigh: {
$gt: [
"$close",
{$multiply: ["$highest_52w", 0.95]} // Within 5% of 52-week high
]
}
}},
{$project: {
date: 1,
close: 1,
volume: 1,
sma_5: {$round: ["$sma_5", 2]},
sma_20: {$round: ["$sma_20", 2]},
priceChange_1d: {$round: ["$priceChange_1d", 2]},
volatility_20d: {$round: ["$volatility_20d", 2]},
signals: {
goldenCross: "$goldenCross",
volumeSpike: "$volumeSpike",
nearHigh: "$nearHigh"
}
}}
]);Comparative Analysis
Period-over-Period Comparisons:
// Year-over-year business performance comparison
db.monthlySales.aggregate([
{$match: {
date: {
$gte: ISODate("2022-01-01"),
$lt: ISODate("2025-01-01")
}
}},
{$addFields: {
year: {$year: "$date"},
month: {$month: "$date"}
}},
{$sort: {date: 1}},
{$setWindowFields: {
partitionBy: "$month", // Partition by month for YoY comparison
sortBy: {year: 1},
output: {
// Previous year same month
previousYearRevenue: {
$shift: {
output: "$revenue",
by: -1
}
},
// Two years ago same month
twoYearsAgoRevenue: {
$shift: {
output: "$revenue",
by: -2
}
},
// 3-year average for the same month
avg3YearRevenue: {
$avg: "$revenue",
window: {documents: [-2, 0]}
}
}
}},
{$addFields: {
yoyGrowth: {
$cond: {
if: {$gt: ["$previousYearRevenue", 0]},
then: {
$multiply: [
{$divide: [
{$subtract: ["$revenue", "$previousYearRevenue"]},
"$previousYearRevenue"
]},
100
]
},
else: null
}
},
twoYearCagr: {
$cond: {
if: {$gt: ["$twoYearsAgoRevenue", 0]},
then: {
$multiply: [
{$subtract: [
{$pow: [
{$divide: ["$revenue", "$twoYearsAgoRevenue"]},
0.5 // Square root for 2-year CAGR
]},
1
]},
100
]
},
else: null
}
}
}},
// Focus on recent data with historical context
{$match: {year: {$gte: 2024}}},
{$project: {
year: 1,
month: 1,
revenue: 1,
previousYearRevenue: 1,
yoyGrowth: {$round: ["$yoyGrowth", 2]},
twoYearCagr: {$round: ["$twoYearCagr", 2]}
}},
{$sort: {year: 1, month: 1}}
]);Data Transformation and ETL
Aggregation pipelines can serve as powerful ETL (Extract, Transform, Load) tools for data processing and preparation.
Data Cleansing and Standardization
Comprehensive Data Cleaning Pipeline:
// Customer data cleansing and standardization
db.rawCustomers.aggregate([
// Extract: Filter out test data and invalid records
{$match: {
email: {$regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/}, // Valid email format
name: {$ne: ""},
name: {$not: {$regex: /test|demo|sample/i}}
}},
// Transform: Clean and standardize data
{$addFields: {
// Clean and standardize names
cleanName: {
$trim: {
input: {
$reduce: {
input: {$split: ["$name", " "]},
initialValue: "",
in: {
$concat: [
"$$value",
{$cond: [
{$eq: ["$$value", ""]},
"",
" "
]},
{$toUpper: {$substr: ["$$this", 0, 1]}},
{$toLower: {$substr: ["$$this", 1, -1]}}
]
}
}
}
}
},
// Standardize email
cleanEmail: {$toLower: {$trim: {input: "$email"}}},
// Parse and validate phone numbers
cleanPhone: {
$let: {
vars: {
digitsOnly: {
$replaceAll: {
input: {$ifNull: ["$phone", ""]},
find: {$regex: /[^\d]/},
replacement: ""
}
}
},
in: {
$cond: {
if: {$eq: [{$strLenCP: "$$digitsOnly"}, 10]},
then: {
$concat: [
"+1-",
{$substr: ["$$digitsOnly", 0, 3]},
"-",
{$substr: ["$$digitsOnly", 3, 3]},
"-",
{$substr: ["$$digitsOnly", 6, 4]}
]
},
else: null
}
}
}
},
// Calculate age from birthdate
age: {
$cond: {
if: {$ne: ["$birthDate", null]},
then: {
$floor: {
$divide: [
{$subtract: [
new Date(),
{$dateFromString: {
dateString: "$birthDate",
onError: null
}}
]},
365.25 * 24 * 60 * 60 * 1000 // Years in milliseconds
]
}
},
else: null
}
}
}},
// Add data quality scores
{$addFields: {
dataQualityScore: {
$add: [
{$cond: [{$ne: ["$cleanPhone", null]}, 20, 0]},
{$cond: [{$ne: ["$age", null]}, 15, 0]},
25, // Base score for having clean data
]
}
}},
// Create final clean structure
{$project: {
_id: 1,
name: "$cleanName",
email: "$cleanEmail",
phone: "$cleanPhone",
age: 1,
dataQualityScore: 1,
cleanedAt: new Date(),
originalId: "$_id"
}},
// Load: Save to clean collection
{$merge: {
into: "customers_clean",
whenMatched: "replace",
whenNotMatched: "insert"
}}
]);Best Practices and Guidelines
Pipeline Design Principles
Effective Aggregation Pipeline Design:
- Filter Early and Often: Use $match stages early to reduce data volume
- Project Necessary Fields: Remove unused fields to reduce memory usage
- Optimize Stage Order: Arrange stages for maximum efficiency
- Use Indexes Strategically: Design pipelines to leverage existing indexes
- Monitor Performance: Use explain() to understand execution plans
// Example of well-optimized pipeline structure
db.orders.aggregate([
// 1. Filter first (uses index)
{$match: {
orderDate: {$gte: ISODate("2025-01-01")},
status: "completed"
}},
// 2. Project early to reduce memory usage
{$project: {
customerId: 1,
orderDate: 1,
total: 1,
items: {
$filter: {
input: "$items",
cond: {$gte: ["$$this.price", 10]} // Filter items in projection
}
}
}},
// 3. Unwind after filtering and projecting
{$unwind: "$items"},
// 4. Group for analytics
{$group: {
_id: "$customerId",
totalSpent: {$sum: "$total"},
avgOrderValue: {$avg: "$total"}
}},
// 5. Sort and limit at the end
{$sort: {totalSpent: -1}},
{$limit: 100}
]);Performance Monitoring
Tracking Pipeline Performance:
// Pipeline performance analysis
function analyzePipelinePerformance(pipeline) {
const explain = db.collection.explain("executionStats").aggregate(pipeline);
return {
totalDocsExamined: explain.stages.reduce((sum, stage) =>
sum + (stage.totalDocsExamined || 0), 0),
totalDocsReturned: explain.stages[explain.stages.length - 1].nReturned,
totalExecutionTime: explain.stages.reduce((sum, stage) =>
sum + (stage.executionTimeMillisEstimate || 0), 0),
indexesUsed: explain.stages
.filter(stage => stage.indexesUsed)
.map(stage => stage.indexesUsed),
memoryUsage: explain.stages
.filter(stage => stage.memUsage)
.map(stage => stage.memUsage)
};
}
// Example usage
const pipeline = [
{$match: {status: "active"}},
{$group: {_id: "$category", count: {$sum: 1}}}
];
const performance = analyzePipelinePerformance(pipeline);
console.log("Pipeline Performance:", performance);Frequently Asked Questions
Q: When should I use aggregation pipelines versus find() queries?
Use aggregation pipelines when you need: Data transformation: Reshaping, calculating, or combining data - Complex analysis: Grouping, statistical calculations, or multi-stage processing - Cross-collection operations: Joining data from multiple collections - Advanced filtering: Complex conditional logic or computed filters
Use find() queries for: Simple document retrieval: Getting documents as-is - Basic filtering: Simple equality or range queries - Better performance: When aggregation overhead isn't justified
// Use find() for simple retrieval
db.products.find({category: "electronics", price: {$lt: 100}});
// Use aggregation for analysis
db.products.aggregate([
{$match: {category: "electronics"}},
{$group: {
_id: "$brand",
avgPrice: {$avg: "$price"},
count: {$sum: 1}
}}
]);Q: How do I optimize slow aggregation pipelines?
Follow these optimization strategies:
- Add $match early: Filter documents before expensive operations
- Create supporting indexes: Design indexes for your pipeline stages
- Use $project to reduce data: Remove unnecessary fields early
- Enable allowDiskUse: For large datasets that exceed memory limits
- Consider $sample: For approximate results on large datasets
// Before optimization (slow)
db.orders.aggregate([
{$unwind: "$items"},
{$lookup: {from: "products", localField: "items.productId", foreignField: "_id", as: "product"}},
{$match: {orderDate: {$gte: ISODate("2025-01-01")}}},
{$group: {_id: "$product.category", total: {$sum: "$items.price"}}}
]);
// After optimization (fast)
db.orders.aggregate([
{$match: {orderDate: {$gte: ISODate("2025-01-01")}}}, // Filter first
{$project: {items: 1}}, // Reduce data early
{$unwind: "$items"},
{$lookup: {from: "products", localField: "items.productId", foreignField: "_id", as: "product"}},
{$group: {_id: "$product.category", total: {$sum: "$items.price"}}}
], {allowDiskUse: true});Q: How do I handle large result sets from aggregation pipelines?
Use these strategies for large results:
- Pagination with $skip and $limit
- Streaming results in application code
- Use $out or $merge to save results to collections
- Implement result caching for repeated queries
// Pagination approach
function getPaginatedResults(page = 1, pageSize = 100) {
return db.collection.aggregate([
{$match: {status: "active"}},
{$group: {_id: "$category", count: {$sum: 1}}},
{$sort: {count: -1}},
{$skip: (page - 1) * pageSize},
{$limit: pageSize}
]);
}
// Save large results to collection
db.orders.aggregate([
{$match: {orderDate: {$gte: ISODate("2024-01-01")}}},
{$group: {_id: "$customerId", totalSpent: {$sum: "$total"}}},
{$out: "customer_totals_2024"} // Save to new collection
]);Q: How do I debug complex aggregation pipelines?
Use these debugging techniques:
- Break pipelines into stages: Test each stage individually
- Use explain(): Understand execution plans and performance
- Add temporary $project stages: Inspect intermediate results
- Use $addFields for debugging: Add temporary fields to track transformations
// Debugging approach - test stages incrementally
// Stage 1
db.orders.aggregate([{$match: {status: "completed"}}]).itcount();
// Stage 1 + 2
db.orders.aggregate([
{$match: {status: "completed"}},
{$unwind: "$items"}
]).itcount();
// Add debugging fields
db.orders.aggregate([
{$match: {status: "completed"}},
{$addFields: {
debug_stage: "after_match",
debug_count: {$size: "$items"}
}},
{$unwind: "$items"},
{$addFields: {
debug_stage: "after_unwind"
}}
]);Q: Can I use aggregation pipelines for real-time analytics?
Yes, with considerations:
Real-time Approaches: Pre-compute common aggregations: Use scheduled jobs to update summary collections - Use change streams: React to data changes and update analytics incrementally - Implement caching: Cache frequently accessed aggregation results - Consider read replicas: Run analytics on secondary nodes
// Pre-computed analytics approach
// Scheduled job to update daily summaries
function updateDailySummaries() {
const today = new Date();
today.setHours(0, 0, 0, 0);
db.orders.aggregate([
{$match: {
orderDate: {$gte: today},
status: "completed"
}},
{$group: {
_id: null,
totalRevenue: {$sum: "$total"},
orderCount: {$sum: 1},
avgOrderValue: {$avg: "$total"}
}},
{$addFields: {
date: today,
updatedAt: new Date()
}},
{$merge: {
into: "daily_summaries",
whenMatched: "replace",
whenNotMatched: "insert"
}}
]);
}
// Change stream for real-time updates
const changeStream = db.orders.watch([
{$match: {"fullDocument.status": "completed"}}
]);
changeStream.on('change', (change) => {
// Update real-time analytics based on the change
updateRealTimeMetrics(change.fullDocument);
});Conclusion
MongoDB's aggregation pipeline represents a powerful framework for complex analytics and data processing directly within the database. By mastering advanced stages, optimization techniques, and real-world patterns, organizations can build sophisticated analytics solutions that scale with their data growth and deliver actionable insights.
The key to successful aggregation pipeline implementation lies in understanding your data patterns, designing efficient stage sequences, and applying appropriate optimization strategies. Whether you're building real-time dashboards, performing complex business intelligence analysis, or implementing ETL processes, the aggregation framework provides the foundation for robust data processing workflows.
Remember that aggregation pipelines are most effective when designed with performance in mind from the beginning. Proper indexing, strategic stage ordering, and memory management ensure that your analytics solutions remain responsive as data volumes grow.
Next Steps
- Analyze your current queries: Identify opportunities to replace multiple queries with single aggregation pipelines
- Design supporting indexes: Create indexes that optimize your aggregation patterns
- Implement monitoring: Track pipeline performance and optimize as needed
- Consider pre-computation: Identify frequently-accessed analytics that can be pre-computed
- Plan for scale: Design pipelines that will perform well as your data grows
About UduLabs
UduLabs brings deep MongoDB aggregation expertise to help organizations unlock the full analytical potential of their data. With years of database experience, our team has designed and optimized complex aggregation pipelines for applications ranging from real-time trading platforms to comprehensive business intelligence systems.
We provide specialized MongoDB aggregation services including pipeline design, performance optimization, real-time analytics implementation, and advanced training programs. Our proven methodologies ensure your aggregation solutions deliver maximum performance while maintaining code clarity and maintainability.
Contact UduLabs to learn how our MongoDB aggregation expertise can help you build powerful analytics solutions that drive business insights and competitive advantage.
* The code snippets provided in this blog are intended as conceptual examples or framework overviews. They are representative and not the complete source code.