基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

MongoDB的聚合管道 (Aggregation Pipeline)?

知识点图片

MongoDB 的 聚合管道 (Aggregation Pipeline) 是 MongoDB 中最强大的功能之一,用于处理数据并返回计算结果。

你可以把它想象成工厂里的流水线:文档(数据)进入管道,经过一个又一个的工序(Stage)处理(如筛选、分组、计算、变形),最后输出你需要的结果。


1. 核心概念

  • 管道 (Pipeline): 由多个阶段(Stage)组成的数组。
  • 阶段 (Stage): 每个阶段对输入的文档进行某种操作(如过滤、分组),并将结果传递给下一个阶段。
  • 表达式 (Expression): 在阶段内部使用的逻辑,用于计算值(如 $sum, $avg, $year 等)。

基本语法:

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

2. 常用阶段 (Common Stages)

以下是最常用的几个阶段,以及它们对应的 SQL 概念:

阶段 (Stage) 描述 SQL 类比
$match 过滤文档,只输出符合条件的文档。 WHERE
$group 将文档分组,可用于统计(求和、平均值等)。 GROUP BY
$project 修改文档结构(选择字段、重命名字段、增加新字段)。 SELECT
$sort 对文档进行排序。 ORDER BY
$limit 限制输出文档的数量。 LIMIT
$skip 跳过指定数量的文档。 OFFSET
$unwind 将数组字段拆分成多条文档(数组中有几个元素就拆成几条)。 (无直接类比,类似 JOIN 自身)
$lookup 执行左外连接,引入其他集合的数据。 LEFT JOIN

3. 实战示例

假设我们有一个 订单集合 (orders),数据结构如下:

json
[
  { "_id": 1, "customer": "Alice", "status": "completed", "total": 100, "items": ["A", "B"] },
  { "_id": 2, "customer": "Bob",   "status": "pending",   "total": 50,  "items": ["C"] },
  { "_id": 3, "customer": "Alice", "status": "completed", "total": 200, "items": ["A"] },
  { "_id": 4, "customer": "Dave",  "status": "completed", "total": 80,  "items": ["B"] }
]

目标:统计每位客户“已完成”订单的总金额,并按金额从高到低排序。

聚合查询代码:

javascript
db.orders.aggregate([
    // 第一步:筛选 ($match)
    // 只保留状态为 "completed" 的订单
    {
        $match: { status: "completed" }
    },

    // 第二步:分组 ($group)
    // 按 customer 分组,计算 total 字段的总和
    {
        $group: {
            _id: "$customer",       // 必须有 _id,代表分组依据
            totalAmount: { $sum: "$total" }, // 创建新字段 totalAmount,值为 total 的累加
            orderCount: { $sum: 1 } // 顺便统计订单数量
        }
    },

    // 第三步:排序 ($sort)
    // 按 totalAmount 降序排列 (-1 表示降序,1 表示升序)
    {
        $sort: { totalAmount: -1 }
    },

    // 第四步:格式化输出 ($project) - 可选
    // 调整输出格式,不显示 _id,改名为 customerName
    {
        $project: {
            _id: 0,                 // 隐藏默认的 _id
            customerName: "$_id",   // 将之前的 _id (即客户名) 赋值给 customerName
            totalAmount: 1,         // 保留 totalAmount
            orderCount: 1           // 保留 orderCount
        }
    }
])

执行流程与结果:

  1. $match: 排除 Bob (pending),剩下 Alice(100), Alice(200), Dave(80)。
  2. $group:
    • Alice: totalAmount = 300
    • Dave: totalAmount = 80
  3. $sort: Alice 排在 Dave 前面。
  4. $project 后 (最终结果):
    json
    [
      { "totalAmount": 300, "orderCount": 2, "customerName": "Alice" },
      { "totalAmount": 80,  "orderCount": 1, "customerName": "Dave" }
    ]

4. 进阶阶段详解

A. $lookup (多表关联)

这是 MongoDB 实现类似 SQL JOIN 的方式。

假设还有一个 products 集合,你想在 orders 里看到商品的详细信息:

javascript
db.orders.aggregate([
   {
      $lookup: {
         from: "products",       // 关联的目标集合
         localField: "items",    // 当前集合(orders)的关联字段
         foreignField: "name",   // 目标集合(products)的关联字段
         as: "productDetails"    // 结果存入这个新数组字段
      }
   }
])

B. $unwind (数组展开)

如果 items["A", "B"],使用 $unwind 后,这一条文档会变成两条:

  1. items: "A"
  2. items: "B"
    这在需要对数组内部元素进行过滤或分组时非常有用。

5. 性能优化建议 (Best Practices)

  1. 尽早过滤 ($match first):
    • 始终将 $match$sort 放在管道的最开始。
    • 原因:这样可以利用 索引 (Index),并且减少后续阶段需要处理的数据量。
  2. 只查询需要的字段:
    • 如果在 $match 之后不需要某些大字段(如大段文本),使用 $project$unset 尽早去掉它们,减少内存消耗。
  3. 内存限制:
    • 默认情况下,聚合管道的一个阶段最多使用 100MB 内存。如果超过,会报错。
    • 解决方法:使用 { allowDiskUse: true } 选项,允许将临时数据写入磁盘(速度会变慢,但不会报错)。

总结

MongoDB 聚合管道是处理复杂数据分析的核心工具。只要记住 "流水线" 这个比喻,明确每一步输入是什么、输出变成了什么,就能轻松掌握它。

00:00
00:00