Skip to content

MongoDB - 聚合

聚合管道(Aggregation Pipeline)是 MongoDB 中用于数据处理的强大框架。它将数据处理建模为一个多阶段管道,文档进入后,通过一系列转换阶段,然后以聚合结果的形式输出。这是现代且高度推荐的替代方案,取代了现在已弃用的 MapReduce 框架。

aggregate() 方法将一个阶段数组作为其参数。每个阶段对文档执行特定操作。一个阶段的输出成为下一个阶段的输入。

db.collection.aggregate([
{ <stage1> },
{ <stage2> },
// ... 更多阶段
]);

让我们使用一个更真实的 orders 集合。每个文档代表一个客户订单。

/* 订单文档示例 */
{
"customerId": "CUST123",
"orderDate": ISODate("2024-07-15T10:00:00Z"),
"status": "completed",
"items": [
{ "productId": "PROD01", "quantity": 2, "price": 25.00 },
{ "productId": "PROD05", "quantity": 1, "price": 100.50 }
],
"totalAmount": 150.50
},
{
"customerId": "CUST456",
"orderDate": ISODate("2024-07-16T14:30:00Z"),
"status": "completed",
"items": [
{ "productId": "PROD01", "quantity": 5, "price": 25.00 }
],
"totalAmount": 125.00
},
{
"customerId": "CUST123",
"orderDate": ISODate("2024-06-20T11:00:00Z"),
"status": "shipped",
"totalAmount": 75.00
}

目标: 查找 2024 年 7 月所有“已完成”订单的总收入,并按客户分组。

db.orders.aggregate([
// 阶段 1:首先过滤相关文档以提高性能
{
$match: {
status: "completed",
orderDate: {
$gte: ISODate("2024-07-01T00:00:00Z"),
$lt: ISODate("2024-08-01T00:00:00Z")
}
}
},
// 阶段 2:按 customerId 分组并计算总计
{
$group: {
_id: "$customerId",
totalRevenue: { $sum: "$totalAmount" },
orderCount: { $sum: 1 }
}
},
// 阶段 3:按最高收入排序
{
$sort: { totalRevenue: -1 }
}
]);

这个管道展示了一个最佳实践:尽早使用 $match 进行过滤,以减少后续更耗性能的阶段(如 $group 和 $sort)处理的文档数量。

StageDescription
$match过滤文档流,只允许匹配的文档进入下一个管道阶段。应尽可能早地放置。
$group根据指定的标识符表达式对输入文档进行分组,并对每个组应用累加器表达式。
$project重塑流中的每个文档,例如添加新字段或删除现有字段。也用于包含/排除字段。
$sort根据指定的排序键重新排序文档流。
$limit将前 n 个文档(n 是指定限制)未经修改地传递到管道中。
$unwind从输入文档中解构一个数组字段,为每个元素输出一个文档。
$lookup对同一数据库中的另一个集合执行左外连接,以将“连接”集合中的文档过滤进来进行处理。
$set (或 $addFields)向文档添加新字段。$set 是 $addFields 的别名。

关键累加器表达式(用于 $group)

Section titled “关键累加器表达式(用于 $group)”
ExpressionDescription
$sum返回数值的总和。可以与值 1 一起使用以计数文档。
$avg计算所有给定数值的平均值。
$min / $max获取对应值的最小值或最大值。
$push返回将表达式应用于组中文档所产生的所有值的数组。
$addToSet返回将表达式应用于组中文档所产生的唯一值的数组。
$first / $last返回将表达式应用于组中第一个/最后一个文档所产生的值。
  • 尽早频繁过滤:在管道的开始处使用 $match。
  • 使用索引:如果您的管道以 $match 或 $sort 开始,请确保这些字段已建立索引,以加快初始阶段的速度。
  • 只投影所需字段:尽早使用 $project 或 $unset 移除不必要的字段,减少阶段之间传递的文档大小。
  • 理解内存限制:复杂的聚合管道会消耗大量内存。对于非常大的数据集,启用 allowDiskUse: true 选项,允许 MongoDB 将数据写入磁盘上的临时文件。