首页 / MongoDB 入门教程 / 聚合实战:多阶段组合

MongoDB 入门教程

聚合实战:多阶段组合

本教程共 50 篇 · 第 35 篇 · 更新于 2026-07-30 · 约 9 分钟阅读

MongoDBMongoDB 入门教程聚合实战多阶段组合统计报表lookup

35. 聚合实战:多阶段组合

本节目标:把前面学的阶段串成完整报表——算各用户的订单总额(带用户名)、算各类商品的销量,以及看订单的月度分布,体会一个真实聚合管道怎么长出来。

前面每章都只讲一个阶段。真实报表往往是好几个阶段接力完成的。这章用三张表做一个电商统计后台,把 $match$lookup$unwind$group$sort$project$count 串起来。

先把数据铺好,后面所有示例都基于它:

db.users.insertMany([
  { _id: 1, name: "张三", city: "北京" },
  { _id: 2, name: "李四", city: "上海" },
  { _id: 3, name: "王五", city: "北京" }
])
db.products.insertMany([
  { _id: 101, name: "机械键盘", category: "外设", price: 399 },
  { _id: 102, name: "无线鼠标", category: "外设", price: 129 },
  { _id: 103, name: "27寸显示器", category: "显示器", price: 1099 }
])
db.orders.insertMany([
  { _id: 1001, user_id: 1, status: "completed", total: 657,
    created_at: ISODate("2024-06-01"),
    items: [ { product: "机械键盘", qty: 1 }, { product: "无线鼠标", qty: 2 } ] },
  { _id: 1002, user_id: 2, status: "completed", total: 1099,
    created_at: ISODate("2024-06-05"),
    items: [ { product: "27寸显示器", qty: 1 } ] },
  { _id: 1003, user_id: 1, status: "shipped", total: 597,
    created_at: ISODate("2024-06-10"),
    items: [ { product: "机械键盘", qty: 1 } ] },
  { _id: 1004, user_id: 3, status: "pending", total: 528,
    created_at: ISODate("2024-07-12"),
    items: [ { product: "无线鼠标", qty: 1 }, { product: "机械键盘", qty: 1 } ] }
])

报表一:各用户的订单总额(带用户名)

思路:先按用户汇总金额,再 $lookup 关联用户名,最后整形。

db.orders.aggregate([
  // 1. 只统计已完成的订单
  { $match: { status: "completed" } },
  // 2. 按用户分组,算每人的总额和订单数
  {
    $group: {
      _id: "$user_id",
      totalAmount: { $sum: "$total" },
      orderCount: { $sum: 1 }
    }
  },
  // 3. 关联 users 表取名字
  {
    $lookup: {
      from: "users",
      localField: "_id",
      foreignField: "_id",
      as: "user"
    }
  },
  // 4. 把名字从数组里取出来,并重命名字段
  {
    $project: {
      _id: 0,
      userName: { $arrayElemAt: [ "$user.name", 0 ] },
      totalAmount: 1,
      orderCount: 1
    }
  },
  // 5. 按总额降序
  { $sort: { totalAmount: -1 } }
])

结果是 [ { userName: "张三", totalAmount: 1254, orderCount: 2 }, { userName: "李四", totalAmount: 1099, orderCount: 1 } ]

Note

$lookupas: "user" 是数组,用 $arrayElemAt: [..., 0] 取第一条。这是关联后取单值的常用套路。

报表二:各类商品的销量

思路:先 $unwind 拆商品行,再关联商品表拿分类,最后按分类汇总数量。

db.orders.aggregate([
  // 1. 拆开订单里的每个商品行
  { $unwind: "$items" },
  // 2. 按商品名关联 products,取分类
  {
    $lookup: {
      from: "products",
      localField: "items.product",
      foreignField: "name",
      as: "p"
    }
  },
  // 3. 把分类从数组里取出来,方便后续分组
  {
    $project: {
      category: { $arrayElemAt: [ "$p.category", 0 ] },
      qty: "$items.qty"
    }
  },
  // 4. 按分类汇总销量
  {
    $group: {
      _id: "$category",
      totalQty: { $sum: "$qty" }
    }
  },
  // 5. 按销量降序
  { $sort: { totalQty: -1 } }
])

结果形如 { _id: "外设", totalQty: 6 }{ _id: "显示器", totalQty: 1 }

Tip

拆数组($unwind)→ 关联($lookup)→ 取字段($project)→ 分组($group),这是处理「数组元素 + 另一张表」的固定四步曲。

报表三:订单的月度分布

思路:用日期表达式取出月份,再按月份分组计数。

db.orders.aggregate([
  // 1. 从 created_at 取年月作为分组键
  {
    $group: {
      _id: {
        year: { $year: "$created_at" },
        month: { $month: "$created_at" }
      },
      orderCount: { $sum: 1 }
    }
  },
  // 2. 按时间升序
  { $sort: { "_id.year": 1, "_id.month": 1 } }
])

结果形如 { _id: { year: 2024, month: 6 }, orderCount: 3 }{ _id: { year: 2024, month: 7 }, orderCount: 1 }

报表四:总订单数(用 $count)

db.orders.aggregate([
  { $match: { status: { $ne: "cancelled" } } },
  { $count: "validOrders" }
])

这里用 $count 一步得到「未取消订单」的总数。

一条流水线的思考顺序

写复杂聚合时,我习惯按这个顺序搭:

  1. $match 先缩数据(能用索引,最快)。
  2. $unwind / $lookup 展开或关联。
  3. $group 做汇总。
  4. $sort 排序。
  5. $skip + $limit 分页。
  6. $project / $count 最后整形输出。
Warning

阶段越靠前,处理的文档越少、越省资源。把 $match$project(减字段)尽量往前放,是聚合性能的基本功。

到这里,MongoDB 的查询、更新、删除与聚合框架的常用阶段我们都过完了。把这些组合好,绝大多数数据统计需求都能在数据库里一站搞定。