美文网首页
MongoDB 快速入门实战教程基础篇 二: 流式聚合操作

MongoDB 快速入门实战教程基础篇 二: 流式聚合操作

作者: you的日常 | 来源:发表于2022-01-06 09:50 被阅读0次

前一部分的文章:
MongoDB 快速入门实战教程基础篇 一 :文档的 CR操作
MongoDB 快速入门实战教程基础篇 一 :文档的 UD操作


基础篇 二 流式聚合操作

信息科学中的聚合是指对相关数据进行内容筛选、处理和归类并输出结果的过程。MongoDB 中的聚合是指同时对多个文档中的数据进行处理、筛选和归类并输出结果的过程。数据在聚合操作的过程中,就像是水流过一节一节的管道一样,所以 MongoDB 中的聚合又被人称为流式聚合。MongoDB 提供了几种聚合方式:

  • Aggregation Pipeline
  • Map-Reduce
  • 简单聚合

接下来,我们将全方位地了解 MongoDB 中的聚合。

Aggregation Pipeline

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 解构文档中的数组字段。

文档、StagePipeline 的关系如下图所示:

在这里插入图片描述

上图描述了文档经过 $match$sample$project 等三个 Stage 并输出的过程。SQL 中常见的聚合术语有 WHERESUMCOUNT 等。下表描述了常见的 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

下面,我们将通过示例了解 AggregateStagePipeline 之间的关系。

概念浅出

$match 的描述为“过滤文档,仅允许匹配的文档地传递到下一个管道阶段”。其语法格式如下:

{ $match: { <query> } }

在开始学习之前,我们需要准备以下数据:

> db.artic.insertMany([
... { "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 },
... { "_id" : 2, "author" : "dave", "score" : 85, "views" : 521 },
... { "_id" : 3, "author" : "anna", "score" : 60, "views" : 706 },
... { "_id" : 4, "author" : "line", "score" : 55, "views" : 300 }
... ])

然后我们建立只有一个 StagePipeline,以实现过滤出 authordave 的文档。对应示例如下:

> db.artic.aggregate([
... {$match: {author: "dave"}}
... ])
{ "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 }
{ "_id" : 2, "author" : "dave", "score" : 85, "views" : 521 }

如果要建立有两个 StagePipeline,那么就在 aggregate 中添加一个 Stage 即可。现在有这样一个需求:统计集合 articscore 大于 70 且小于 90 的文档数量。这个需求分为两步进行:

  • 过滤出符合要求的文档
  • 统计文档数量

Aggregation 非常适合这种多步骤的操作。在这个场景中,我们需要用到 $match$group 这两个 Stage ,然后再与聚合表达式 $sum 相结合,对应示例如下:

> db.artic.aggregate([
... {$match: {score: {$gt: 70, $lt: 90}}},
... {$group: {_id: null, number: {$sum: 1}}}
... ])
{ "_id" : null, "number" : 2 }

这个示例的完整过程可以用下图表示: 在这里插入图片描述

通过上面的描述和举例,我相信你对 AggregateStagePipeline 有了一定的了解。接下来,我们将学习常见的 Stage 的语法和用途。

常见的 Stage

sample

$sample 的作用是从输入中随机选择指定数量的文档,其语法格式如下:

{ $sample: { size: <positive integer> } }

假设要从集合 artic 中随机选择两个文档,对应示例如下:

> db.artic.aggregate([
... {$sample: {size: 2}}
... ])
{ "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 }
{ "_id" : 3, "author" : "anna", "score" : 60, "views" : 706 }

size 对应的值必须是正整数,如果输入负数会得到错误提示:size argument to $sample must not be negative。要注意的是,当值超过集合中的文档数量时,返回结果是集合中的所有文档,但文档顺序是随机的。

project

$project 的作用是过滤文档中的字段,这与投影操作相似,但处理结果将会传入到下一个阶段 。其语法格式如下:

{ $project: { <specification(s)> } }

准备以下数据:

> db.projects.save(
    {_id: 1, title: "篮球训练营青春校园活动开始啦", numb: "A829Sck23", author: {last: "quinn", first: "James"}, hot: 35}
)

假设 Pipeline 中的下一个 Stage 只需要文档中的 titleauthor 字段,对应示例如下:

> db.projects.aggregate([{$project: {title: 1, author: 1}}])
{ "_id" : 1, "title" : "篮球训练营青春校园活动开始啦", "author" : { "last" : "quinn", "first" : "James" } }

01 可以同时存在。对应示例如下:

> db.projects.aggregate([{$project: {title: 1, author: 1, _id: 0}}])
{ "title" : "篮球训练营青春校园活动开始啦", "author" : { "last" : "quinn", "first" : "James" } }

true 等效于 1false 等效于 0,也可以混用布尔值和数字,对应示例如下:

> db.projects.aggregate([{$project: {title: 1, author: true, _id: false}}])
{ "title" : "篮球训练营青春校园活动开始啦", "author" : { "last" : "quinn", "first" : "James" } }

如果想要排除指定字段,那么在 $project 中将其设置为 0false 即可,对应示例如下:

> db.projects.aggregate([{$project: {author: false, _id: false}}])
{ "title" : "篮球训练营青春校园活动开始啦", "numb" : "A829Sck23", "hot" : 35 }

$project 也可以作用于嵌入式文档。对于 author 字段,有时候我们只需要 FirstName 或者 Lastname ,对应示例如下:

> db.projects.aggregate([{$project: {author: {"last": false}, _id: false, numb: 0}}])
{ "title" : "篮球训练营青春校园活动开始啦", "author" : { "first" : "James" }, "hot" : 35 }

这里使用 {author: {"last": false}} 过滤掉 LastName,但保留 first

以上就是 $project 的基本用法和作用介绍,更多与 $project 相关的知识可查阅官方文档 $project

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>);

lookup 支持的指令及对应描述如下:

领域 描述
from 指定集合名称。
localField 指定输入 $lookup 中的字段。
foreignField 指定from 给定的集合中的文档字段。
as 指定要添加到输入文档的新数组字段的名称。

新数组字段包含from集合中的匹配文档。
如果输入文档中已存在指定的名称,则会覆盖现有字段 。 |

准备以下数据:

> db.sav.insert([
   { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
   { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
   { "_id" : 3  }
])

> db.avi.insert([
   { "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
   { "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
   { "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
   { "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
   { "_id" : 5, "sku": null, description: "Incomplete" },
   { "_id" : 6 }
])

假设要连接集合 sav 中的 item 和集合 avi 中的 sku,并将连接结果命名为 savi。对应示例如下:

> db.sav.aggregate([
   {
     $lookup:
       {
         from: "avi",
         localField: "item",
         foreignField: "sku",
         as: "savi"
       }
  }
])

命令执行后,输出如下内容:

{
   "_id" : 1,
   "item" : "almonds",
   "price" : 12,
   "quantity" : 2,
   "savi" : [
      { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
   ]
}
{
   "_id" : 2,
   "item" : "pecans",
   "price" : 20,
   "quantity" : 1,
   "savi" : [
      { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
   ]
}
{
   "_id" : 3,
   "savi" : [
      { "_id" : 5, "sku" : null, "description" : "Incomplete" },
      { "_id" : 6 }
   ]
}

上面的连接操作等效于下面这样的伪 SQL:

SELECT *, savi
FROM sav
WHERE savi IN (SELECT *
FROM avi
WHERE sku= sav.item);

以上就是 lookup 的基本用法和作用介绍,更多与 lookup 相关的知识可查阅官方文档 lookup

unwind

unwind 能将包含数组的文档拆分称多个文档,其语法格式如下:

{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}

unwind 支持的指令及对应描述如下:

指令 类型 描述
path string 指定数组字段的字段路径, 必填。
includeArrayIndex string 用于保存元素的数组索引的新字段的名称。
preserveNullAndEmptyArrays boolean 默认情况下,如果pathnull、缺少该字段或空数组, 则不输出文档。反之,将其设为 true 则会输出文档。

在开始学习之前,我们需要准备以下数据:

> db.shoes.save({_id: 1, brand: "Nick", sizes: [37, 38, 39]})

集合 shoes 中的 sizes 是一个数组,里面有多个尺码数据。假设要将这个文档拆分成 3 个 size 为单个值的文档,对应示例如下:

> db.shoes.aggregate([{$unwind : "$sizes"}])
{ "_id" : 1, "brand" : "Nick", "sizes" : 37 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 38 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 39 }

显然,这样的文档更方便我们做数据处理。preserveNullAndEmptyArrays 指令默认为 false,也就是说文档中指定的 path 为空、null 或缺少该 path 的时候,会忽略掉该文档。假设数据如下:

> db.shoes2.insertMany([
{"_id": 1, "item": "ABC", "sizes": ["S", "M", "L"]},
{"_id": 2, "item": "EFG", "sizes": [ ]},
{"_id": 3, "item": "IJK", "sizes": "M"},
{"_id": 4, "item": "LMN" },
{"_id": 5, "item": "XYZ", "sizes": null}
])

我们执行以下命令:

> db.shoes2.aggregate([{$unwind: "$sizes"}])

就会得到如下输出:

{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

_id245 的文档由于满足 preserveNullAndEmptyArrays 的条件,所以不会被拆分。

以上就是 unwind 的基本用法和作用介绍,更多与 unwind 相关的知识可查阅官方文档 unwind

out

相关文章

网友评论

      本文标题:MongoDB 快速入门实战教程基础篇 二: 流式聚合操作

      本文链接:https://www.haomeiwen.com/subject/zrnhcrtx.html