Files
Cloud-book/数据库/MongoDB_2025/MongoDB聚合框架.md
2025-08-27 17:10:05 +08:00

7.4 KiB
Raw Blame History

聚合框架

聚合框架是 MongoDB 提供的一个强大的数据处理工具,它允许对集合中的数据进行一系列的转换和计算,最终得到聚合后的结果。本章节将深入探讨聚合管道、常用的聚合阶段以及如何利用聚合框架进行复杂的数据分析。


聚合管道Aggregation Pipeline

聚合操作的核心是聚合管道。管道由一个或多个阶段 (Stage) 组成,每个阶段都会对输入的文档流进行处理,并将结果传递给下一个阶段。

聚合管道的语法

聚合操作使用 aggregate() 方法,其参数是一个包含所有阶段的数组。

db.collection.aggregate([
  { <stage1> },
  { <stage2> },
  ...
])

聚合管道的优势

  • 功能强大: 支持复杂的数据转换、分组、计算和重塑。
  • 性能高效: 许多操作在数据库服务端以原生代码执行,减少了数据在网络中的传输。
  • 灵活性高: 可以通过组合不同的阶段来满足各种复杂的数据处理需求。

常用聚合阶段

以下是一些最常用的聚合阶段,通过组合它们可以实现强大的数据处理能力。

$match

  • 功能: 过滤文档,只将满足条件的文档传递给下一个阶段。类似于 find() 方法的查询条件。

  • 建议: 尽可能将 $match 放在管道的开头,以尽早减少需要处理的文档数量,提高效率。

  • 示例: 筛选出状态为 "A" 的订单。

    { $match: { status: "A" } }
    

$project

  • 功能: 重塑文档流。可以包含、排除、重命名字段,或者通过表达式计算新字段。

  • 示例: 只保留 _idname 字段,并创建一个新的 bonus 字段。

    { $project: { name: 1, bonus: { $multiply: ["$salary", 0.1] } } }
    

$group

  • 功能: 按指定的 _id 表达式对文档进行分组,并对每个分组应用累加器表达式进行计算。

  • 核心: _id 字段定义了分组的键。

  • 示例: 按 cust_id 分组,并计算每个客户的订单总金额。

    {
      $group: {
        _id: "$cust_id",
        totalAmount: { $sum: "$amount" }
      }
    }
    

$sort

  • 功能: 对文档流进行排序,与 find() 中的 sort() 类似。

  • 建议: 如果需要排序,尽量在管道的早期阶段进行,特别是当排序字段有索引时。

  • 示例: 按 totalAmount 降序排序。

    { $sort: { totalAmount: -1 } }
    

$limit$skip

  • 功能: 分别用于限制和跳过文档数量,实现分页。

  • 示例: 返回排序后的前 5 个结果。

    { $limit: 5 }
    

$unwind

  • 功能: 将文档中的数组字段拆分成多个文档,数组中的每个元素都会生成一个新的文档(与其他字段组合)。

  • 示例: 将 tags 数组拆分。

    // 输入: { _id: 1, item: "A", tags: ["x", "y"] }
    { $unwind: "$tags" }
    // 输出:
    // { _id: 1, item: "A", tags: "x" }
    // { _id: 1, item: "A", tags: "y" }
    

$lookup

  • 功能: 实现左外连接Left Outer Join将当前集合与另一个集合的文档进行关联。

  • 示例: 将 orders 集合与 inventory 集合关联起来。

    {
      $lookup: {
        from: "inventory",
        localField: "item",
        foreignField: "sku",
        as: "inventory_docs"
      }
    }
    

聚合累加器表达式

累加器主要在 $group 阶段使用,用于对分组后的数据进行计算。

累加器 描述
$sum 计算总和
$avg 计算平均值
$min 获取最小值
$max 获取最大值
$first 获取每个分组的第一条文档的字段值
$last 获取每个分组的最后一条文档的字段值
$push 将字段值添加到一个数组中
$addToSet 将唯一的字段值添加到一个数组中

聚合管道优化

  • 尽早过滤: 将 $match 阶段放在管道的最前面。
  • 尽早投影: 使用 $project 移除不需要的字段,减少后续阶段的数据处理量。
  • 利用索引: 如果 $match$sort 阶段的字段有索引MongoDB 可以利用它来优化性能。
  • 避免在分片键上 $unwind: 这可能会导致性能问题。

实践环节

需求描述

假设有一个 sales 集合,包含 product, quantity, price, date 字段。

  1. 计算总销售额: 计算每个产品的总销售额(quantity * price)。
  2. 查找最畅销产品: 按销售额降序排列,找出销售额最高的前 5 个产品。
  3. 按月统计销售: 按月份对所有销售数据进行分组,并计算每月的总销售额和平均订单金额。
  4. 关联查询: 假设还有一个 products_for_aggregation 集合(包含 name, category),使用 $lookup 将销售数据与产品类别关联起来,并按类别统计销售额。

实践细节和结果验证

// 准备工作:确保已在 mongo shell 中加载 data.js 文件

// 1. 计算每个产品的总销售额
db.sales.aggregate([
  {
    $group: {
      _id: "$product",
      totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
    }
  }
])
/*
  预期结果:
  [
    { _id: 'Mouse', totalRevenue: 125 },
    { _id: 'Keyboard', totalRevenue: 75 },
    { _id: 'Monitor', totalRevenue: 300 },
    { _id: 'Webcam', totalRevenue: 50 },
    { _id: 'Laptop', totalRevenue: 4700 }
  ]
*/

// 2. 查找最畅销的前 5 个产品(按销售额)
db.sales.aggregate([
  {
    $group: {
      _id: "$product",
      totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
    }
  },
  {
    $sort: { totalRevenue: -1 }
  },
  {
    $limit: 5
  }
])
/*
  预期结果:
  [
    { _id: 'Laptop', totalRevenue: 4700 },
    { _id: 'Monitor', totalRevenue: 300 },
    { _id: 'Mouse', totalRevenue: 125 },
    { _id: 'Keyboard', totalRevenue: 75 },
    { _id: 'Webcam', totalRevenue: 50 }
  ]
*/

// 3. 按月统计销售额
db.sales.aggregate([
  {
    $group: {
      _id: { $month: "$date" }, // 按月份分组
      totalMonthlyRevenue: { $sum: { $multiply: ["$quantity", "$price"] } },
      averageOrderValue: { $avg: { $multiply: ["$quantity", "$price"] } }
    }
  },
  {
    $sort: { _id: 1 } // 按月份升序排序
  }
])
/*
  预期结果:
  [
    { _id: 1, totalMonthlyRevenue: 1325, averageOrderValue: 441.666... },
    { _id: 2, totalMonthlyRevenue: 1675, averageOrderValue: 558.333... },
    { _id: 3, totalMonthlyRevenue: 2250, averageOrderValue: 1125 }
  ]
*/

// 4. 关联查询:按产品类别统计销售额
db.sales.aggregate([
  // 阶段一: 计算每笔销售的销售额
  {
    $project: {
      product: 1,
      revenue: { $multiply: ["$quantity", "$price"] }
    }
  },
  // 阶段二: 关联 products 集合获取类别信息
  {
    $lookup: {
      from: "products_for_aggregation",
      localField: "product",
      foreignField: "name",
      as: "productDetails"
    }
  },
  // 阶段三: 展开 productDetails 数组
  {
    $unwind: "$productDetails"
  },
  // 阶段四: 按类别分组统计总销售额
  {
    $group: {
      _id: "$productDetails.category",
      totalCategoryRevenue: { $sum: "$revenue" }
    }
  }
])
/*
  预期结果:
  [
    { _id: 'Accessories', totalCategoryRevenue: 50 },
    { _id: 'Electronics', totalCategoryRevenue: 5200 }
  ]
*/