Skip to content
All essays
ArchitectureFebruary 28, 202517 min

MongoDB Aggregation Framework: Advanced Pipelines and Data Transformation

Master MongoDB aggregation framework. Learn advanced pipelines, data transformation, grouping, and complex queries for powerful data analysis.

Ü
Ümit Uz
Mobile & Full Stack Developer

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:

javascript
// 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

javascript
// 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:

javascript
// Filter active users
db.users.aggregate([
  {
    $match: {
      status: 'active',
      age: { $gte: 18 }
    }
  }
]);

$group

Group documents by a specified expression:

javascript
// 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:

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

javascript
// 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:

javascript
// Flatten order items
db.orders.aggregate([
  {
    $unwind: '$items'
  },
  {
    $group: {
      _id: '$items.productId',
      totalQuantity: { $sum: '$items.quantity' },
      totalRevenue: {
        $sum: {
          $multiply: ['$items.quantity', '$items.price']
        }
      }
    }
  }
]);

Multiple aggregations in one query:

javascript
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

javascript
// 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

javascript
// 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

javascript
// 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

javascript
// 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

javascript
// 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

javascript
// Allow disk usage for large datasets
db.orders.aggregate(
  [
    { $group: { _id: null, total: { $sum: '$total' } } }
  ],
  { allowDiskUse: true }
);

Real-World Examples

E-commerce Analytics

javascript
// 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

javascript
// 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

javascript
// 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

javascript
// 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:

javascript
// Find all descendants
db.categories.aggregate([
  {
    $graphLookup: {
      from: 'categories',
      startWith: '$_id',
      connectFromField: '_id',
      connectToField: 'parentId',
      as: 'descendants'
    }
  }
]);

Redact

Conditional document filtering:

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

javascript
db.orders.aggregate(
  [
    { $group: { _id: '$userId', total: { $sum: '$total' } } }
  ],
  { explain: true }
);

5. Limit Memory Usage

Use allowDiskUse for large datasets:

javascript
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.

Related essays

Next essay
Git Workflows: Branching Strategies for Teams