MongoDB 中的聚合是指同时对多个文档中的数据进行处理、筛选和归类并输出结果的过程。数据在聚合操作的过程中,就像是水流过一节一节的管道一样,所以 MongoDB 中的聚合又被人称为流式聚合。
Aggregation Pipeline 又称聚合管道。开发者可以将多个文档传入一个由多个 Stage 组成的 Pipeline,每一个 Stage 处理的结果将会传入下一个 Stage 中,最后一个 Stage 的处理结果就是整个 Pipeline 的输出。
创建聚合管道的语法如下:
db.collection.aggregate( [ { <stage> }, ... ] )
MongoDB 提供了 23 种 Stage,它们是:
Stage | 描述 |
---|---|
$addFields | 向文档添加新字段 |
$bucket | 根据指定的表达式和存储区边界将传入的文档分组 |
$bucketAuto | 根据指定的表达式将传入的文档分类为特定数量的组,自动确定存储区边界 |
$collStats | 返回有关集合或视图的统计信息 |
$count | 返回聚合管道此阶段的文档数量计数 |
$facet | 在同一组输入文档的单个阶段内处理多个聚合操作 |
$geoNear | 基于与地理空间点的接近度返回有序的文档流 |
$graphLookup | 对集合执行递归搜索 |
$group | 按指定的标识符表达式对文档进行分组 |
$indexStats | 返回集合的索引信息 |
$limit | 将未修改的前 n 个文档传递给管道 |
$listSessions | 列出system.sessions集合的所有会话 |
$lookup | 对同一数据库中的另一个集合执行左外连接 |
$match | 过滤文档,仅允许匹配的文档地传递到下一个管道阶段 |
$out | 将聚合管道的结果文档写入指定集合,它必须是管道中的最后一个阶段 |
$project | 为文档添加新字段或删除现有字段 |
$redact | 可用于实现字段级别的编辑 |
$replaceRoot | 用指定的嵌入文档替换文档该操作将替换输入文档中的所有现有字段,包括_id字段指定嵌入在输入文档中的文档以将嵌入文档提升到顶层 |
$sample | 从输入中随机选择指定数量的文档 |
$skip | 跳过前 n 个文档,并将未修改的其余文档传递到下一个阶段 |
$sort | 按指定的排序键重新排序文档流只有订单改变; 文件保持不变对于每个输入文档,输出一个文档 |
$sortByCount | 对传入文档进行分组,然后计算每个不同组中的文档计数 |
$unwind | 解构文档中的数组字段 |
文档、Stage 和 Pipeline 的关系如下图所示:
上图描述了文档经过
m
a
t
c
h
、
match、
match、sample 和 $project 等三个 Stage 并输出的过程。
SQL 中常见的聚合术语有 WHERE、SUM 和 COUNT 等。下表描述了常见的 SQL 聚合术语、函数和概念以及对应的 MongoDB 操作符或 Stage。
SQL | MongoDB |
---|---|
WHERE | $match |
GROUP BY | $group |
HAVING | $match |
SELECT | $project |
ORDER BY | $sort |
LIMIT | $limit |
SUM() | $sum |
COUNT() | $ sum $sortByCount |
join | $lookup |
$match
过滤文档,让符合条件的文档进入下一个管道阶段。语法格式如下:
{ $match: { <query> } }
准备数据:
db.getCollection('test').insertMany([
{ "_id" : 1, "author" : "dave", "score" : 80, "views" : 100, "tags":["A","B","C"] },
{ "_id" : 2, "author" : "dave", "score" : 85, "views" : 521, "tags":["A","B","C"] },
{ "_id" : 3, "author" : "ahn", "score" : 60, "views" : 1000 },
{ "_id" : 4, "author" : "li", "score" : 55, "views" : 5000 , "tags":["B","C","D"]},
{ "_id" : 5, "author" : "annT", "score" : 60, "views" : 50, "tags":["B","C","D"] },
{ "_id" : 6, "author" : "li", "score" : 94, "views" : 999 },
{ "_id" : 7, "author" : "ty", "score" : 95, "views" : 1000, "tags":["E","F"] }
])
查询author是dave的:
db.getCollection('test').aggregate([
{$match:{"author":"dave"}}
])
返回数据:
/* 1 */
{
"_id" : 1.0,
"author" : "dave",
"score" : 80.0,
"views" : 100.0
}
/* 2 */
{
"_id" : 2.0,
"author" : "dave",
"score" : 85.0,
"views" : 521.0
}
score大于70小于90或者views大于等1000的文档数量之和:
db.getCollection('test').aggregate([
{$match:{$or:[{"score":{$gt:70,$lt:90}},{"views":{$gte:1000}}]}},
{$group:{"_id":null,count:{$sum:1}}}
])
返回数据:
{
"_id" : null,
"count" : 5.0
}
$sample
$sample 的作用是从输入中随机选择指定数量的文档,其语法格式如下:
{ $sample: { size: <positive integer> } }
示例:
db.getCollection('test').aggregate([
{$sample:{size:3}}
])
$project
$project是筛选文档中的字段,将结果传入下一个阶段。格式如下:
{ $project: { <specification(s)> } }
只获取_id和score(_id也可以不写):
db.getCollection('test').aggregate([
{$sample:{size:3}},
{$project:{"_id":1, "score":1}}
])
0或false表示不显示,1或者true表示显示。
db.getCollection('test').aggregate([
{$sample:{size:3}},
{$project:{"score":1,"author":true, "_id":false}}
])
$unwind
$unwind 能将包含数组的文档拆分称多个文档,其语法格式如下:
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
unwind 支持的指令及对应描述如下:
指令 | 类型 | 描述 |
---|---|---|
path | string | 指定数组字段的字段路径, 必填。 |
includeArrayIndex | string | 用于保存元素的数组索引的新字段的名称 |
preserveNullAndEmptyArrays | boolean | 默认情况下,如果path为 null、缺少该字段或空数组, 则不输出文档。反之,将其设为 true 则会输出文档 |
按照tags进行拆分:
db.getCollection('test').aggregate([
{
$unwind:"$tags"
}
])
返回结果中没有tags不存在的文档。如果需要显示,可以把preserveNullAndEmptyArrays设置为true.
db.getCollection('test').aggregate([
{
$unwind:{path:"$tags", preserveNullAndEmptyArrays:true}
}
])
如果需要显示数组索引并命名(tags的数组索引):
db.getCollection('test').aggregate([
{
$unwind:{path:"$tags",includeArrayIndex: "arrayIndex", preserveNullAndEmptyArrays:true}
}
])
$out
out 的作用是聚合 Pipeline 返回的结果文档,并将其写入指定的集合。要注意的是,out 操作必须出现在 Pipeline 的最后。out 语法格式如下:
{ $out: "<output-collection>" }
比如将过滤出的文档放入test2中:
db.getCollection('test').aggregate([
{
$unwind:"$tags"
},
{
$project:{"_id":0, "author":1, "score": 1, "views":1, "tags":1}
},
{
$out:"test2"
}
])
$lookup
$lookup 的作用是对同一数据库中的集合执行左外连接,其语法格式如下:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
与下面sql写法类似:
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
FROM <collection to join>
WHERE <foreignField>= <collection.localField>);
指令及对应描述如下:
领域 | 描述 |
---|---|
from | 指定集合名称。 |
localField | 指定输入 $lookup 中的字段。 |
foreignField | 指定from 给定的集合中的文档字段。 |
as | 指定要添加到输入文档的新数组字段的名称。新数组字段包含from集合中的匹配文档。如果输入文档中已存在指定的名称,则会覆盖现有字段 |
准备数据:
db.getCollection('test2').insertMany([
{"_id" : 1, "pid": 1, "name" : "dave book", "tag":"A" },
{ "_id" : 2, "pid": 1, "name" : "dave book", "tag":"B" },
{ "_id" : 3, "pid": 2, "name" : "dave book", "tag":"B"}
])
查询test._id和test2.pid相等的文档:
db.getCollection('test').aggregate([
{$lookup:{
from :"test2",
localField:"_id",
foreignField:"pid",
as:"item"
}}
])
返回数据如下:
{
"_id" : 1.0,
"author" : "dave",
"score" : 80.0,
"views" : 100.0,
"tags" : [
"A",
"B",
"C"
],
"item" : [
{
"_id" : 1.0,
"pid" : 1.0,
"name" : "dave book",
"tag" : "A"
},
{
"_id" : 2.0,
"pid" : 1.0,
"name" : "dave book",
"tag" : "B"
}
]
}
查询test.tags的数据和test2.tag相等的文档:
db.getCollection('test').aggregate([
{$lookup:{
from :"test2",
localField:"tags",
foreignField:"tag",
as:"item"
}}
])
返回如下:
{
"_id" : 1.0,
"author" : "dave",
"score" : 80.0,
"views" : 100.0,
"tags" : [
"A",
"B",
"C"
],
"item" : [
{
"_id" : 1.0,
"pid" : 1.0,
"name" : "dave book",
"tag" : "A"
},
{
"_id" : 2.0,
"pid" : 1.0,
"name" : "dave book",
"tag" : "B"
},
{
"_id" : 3.0,
"pid" : 2.0,
"name" : "dave book",
"tag" : "B"
}
]
}