MongoDB聚合框架
7.1 聚合框架概述
7.1.1 什么是聚合框架
聚合框架是MongoDB中用于数据聚合、转换和分析的强大工具,类似于SQL中的GROUP BY、JOIN、聚合函数等功能的组合。它基于管道(Pipeline) 概念,通过一系列阶段(Stage) 对文档进行处理,最终返回计算结果。
7.1.2 核心优势
- 声明式语法,易于理解
- 支持多阶段组合,灵活处理复杂数据
- 内部优化,性能高效
- 可直接将结果写入集合,便于后续使用
7.2 管道、阶段和可调参数
7.2.1 管道
管道是一系列阶段的序列,每个阶段对输入的文档执行某种转换,输出结果作为下一阶段的输入。数据在管道中依次流转,最终返回处理后的结果。
javascript
db.collection.aggregate([
{ $match: { status: "A" } }, // 阶段1:过滤
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }, // 阶段2:分组聚合
{ $sort: { total: -1 } } // 阶段3:排序
]);
7.2.2 阶段
每个阶段完成特定的数据处理任务。常见阶段包括:
$match:过滤文档$project:重塑文档结构$group:分组聚合$sort:排序$limit/$skip:分页$unwind:拆分数组$lookup:关联查询$addFields:添加新字段$out/$merge:输出结果
7.2.3 可调参数
MongoDB允许对聚合操作进行性能调优:
allowDiskUse:允许使用磁盘存储临时数据,避免内存限制(默认内存限制为100MB)explain:查看聚合管道的执行计划,分析性能瓶颈hint:强制使用指定索引
javascript
db.orders.aggregate(pipeline, { allowDiskUse: true });
7.3 阶段入门:常见操作
7.3.1 $match:过滤数据
类似于SQL中的WHERE子句,尽早使用可以缩小数据量,提升性能。
javascript
{ $match: { status: "shipped", amount: { $gt: 100 } } }
7.3.2 $sort:排序
在内存中进行排序,如果数据量大且启用了allowDiskUse,可以使用磁盘排序。
javascript
{ $sort: { createdAt: -1, amount: 1 } }
7.3.3 $limit 和 $skip:分页
javascript
{ $skip: 10 },
{ $limit: 10 }
7.3.4 $lookup:关联查询
实现类似SQL的LEFT OUTER JOIN功能。
javascript
{
$lookup: {
from: "inventory",
localField: "productId",
foreignField: "_id",
as: "productDetails"
}
}
7.3.5 $addFields:添加字段
为文档添加新字段,不改变原有文档结构。
javascript
{ $addFields: { totalPrice: { $multiply: ["$price", "$quantity"] } } }
7.4 表达式
7.4.1 概述
表达式用于在阶段内部执行计算、比较、逻辑判断等操作。它们可以用于字段投影、条件判断、数学运算等场景。
7.4.2 表达式类型
- 布尔表达式 :
$and,$or,$not - 比较表达式 :
$eq,$gt,$lt,$gte,$lte,$ne - 条件表达式 :
$cond,$ifNull,$switch - 算术表达式 :
$add,$subtract,$multiply,$divide,$mod - 字符串表达式 :
$concat,$substr,$toUpper,$toLower - 日期表达式 :
$year,$month,$dayOfMonth,$dateToString - 数组表达式 :
$size,$slice,$concatArrays,$filter
7.4.3 示例
javascript
{
$project: {
fullName: { $concat: ["$firstName", " ", "$lastName"] },
isAdult: { $gte: ["$age", 18] },
birthYear: { $year: "$birthDate" }
}
}
7.5 $project
7.5.1 作用
- 选择需要输出的字段(类似SQL的
SELECT) - 重命名字段
- 计算新字段
- 排除字段
7.5.2 基本语法
javascript
{ $project: { field1: 1, field2: 1, newField: { $add: ["$a", "$b"] }, _id: 0 } }
7.5.3 常用场景
javascript
// 选择字段
{ $project: { name: 1, email: 1 } }
// 重命名字段
{ $project: { fullName: "$name", age: 1 } }
// 计算新字段
{ $project: { total: { $multiply: ["$price", "$qty"] } } }
// 嵌套字段投影
{ $project: { "address.city": 1 } }
7.6 $unwind
7.6.1 作用
将数组字段拆分成多个文档,每个文档包含数组中的一个元素。常用于数组扁平化处理。
7.6.2 基本语法
javascript
{ $unwind: "$items" } // 拆解items数组
7.6.3 高级选项
javascript
{
$unwind: {
path: "$tags", // 要拆解的数组字段
includeArrayIndex: "index", // 保存数组索引
preserveNullAndEmptyArrays: true // 保留空数组或缺失字段的文档
}
}
7.6.4 应用场景
- 统计数组中每个元素的出现频率
- 将嵌套数组数据展平以便后续聚合
- 处理标签、评论等多值字段
7.7 数组表达式
7.7.1 常用数组操作符
$size:返回数组元素个数$slice:截取数组部分元素$arrayElemAt:获取指定索引的元素$filter:过滤数组元素$map:对数组每个元素应用表达式$reduce:归约数组$concatArrays:合并多个数组$in:检查元素是否在数组中
7.7.2 示例
javascript
{
$project: {
itemCount: { $size: "$items" },
firstItem: { $arrayElemAt: ["$items", 0] },
highValueItems: {
$filter: {
input: "$items",
as: "item",
cond: { $gt: ["$$item.price", 100] }
}
}
}
}
7.8 累加器
7.8.1 概述
累加器用于在$group阶段对分组内的数据进行聚合计算。它们在文档流经管道时维护状态,直到分组完成。
7.8.2 常用累加器
| 累加器 | 说明 | 示例 |
|---|---|---|
$sum |
求和 | { $sum: "$amount" } |
$avg |
平均值 | { $avg: "$score" } |
$min / $max |
最小值/最大值 | { $min: "$price" } |
$first / $last |
第一个/最后一个值 | { $first: "$name" } |
$push |
收集所有值到数组 | { $push: "$product" } |
$addToSet |
收集唯一值到数组 | { $addToSet: "$category" } |
$stdDevPop / $stdDevSamp |
标准差 | { $stdDevPop: "$score" } |
7.8.3 示例
javascript
{
$group: {
_id: "$category",
totalSales: { $sum: "$amount" },
avgPrice: { $avg: "$price" },
products: { $addToSet: "$productName" },
count: { $sum: 1 }
}
}
7.9 分组简介
7.9.1 $group阶段
$group是聚合框架中最核心的阶段之一,用于按指定字段分组,并对每组执行聚合计算。
7.9.2 基本语法
javascript
{
$group: {
_id: <expression>, // 分组依据
<field1>: { <accumulator1>: <expression1> },
<field2>: { <accumulator2>: <expression2> }
}
}
7.9.3 分组依据
- 单字段 :
_id: "$category" - 多字段 :
_id: { year: "$year", month: "$month" } - 常量 :
_id: null(全局聚合) - 表达式 :
_id: { $toUpper: "$city" }
7.9.4 示例
javascript
db.orders.aggregate([
{
$group: {
_id: { year: { $year: "$orderDate" }, status: "$status" },
totalAmount: { $sum: "$amount" },
orderCount: { $sum: 1 },
avgAmount: { $avg: "$amount" }
}
},
{ $sort: { "_id.year": -1, totalAmount: -1 } }
]);
7.10 将聚合管道结果写入集合中
7.10.1 $out 阶段
将聚合结果写入指定的集合(会替换原有集合的内容)。
javascript
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $out: "customer_totals" }
]);
7.10.2 $merge 阶段
更灵活的输出方式,可以将结果合并到现有集合(MongoDB 4.2+)。
javascript
db.orders.aggregate([
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{
$merge: {
into: "customer_summary",
on: "_id",
whenMatched: "replace",
whenNotMatched: "insert"
}
}
]);
7.10.3 使用场景
- 物化聚合结果,提升后续查询性能
- 创建报表、仪表板数据源
- 数据仓库ETL流程
总结
本章系统介绍了MongoDB聚合框架的核心内容:
| 类别 | 知识点 |
|---|---|
| 基础概念 | 管道、阶段、可调参数(allowDiskUse、explain) |
| 常见阶段 | match、match、match、sort、limit、limit、limit、skip、lookup、lookup、lookup、addFields |
| 表达式 | 布尔、比较、条件、算术、字符串、日期、数组表达式 |
| 字段操作 | $project(字段投影、计算、重命名) |
| 数组处理 | unwind(数组拆解)、数组表达式(unwind(数组拆解)、数组表达式(unwind(数组拆解)、数组表达式(filter、$map等) |
| 聚合计算 | 累加器(sum、sum、sum、avg、push等)、push等)、push等)、group分组 |
| 结果输出 | out(覆盖写入)、out(覆盖写入)、out(覆盖写入)、merge(合并写入) |
聚合框架是MongoDB实现复杂数据分析的核心能力,掌握这些知识点能够帮助开发者高效处理数据聚合、报表统计、数据清洗等场景。