MongoDB聚合操作

聚合操作概述

聚合操作允许用户处理多个文档并返回计算结果。聚合操作组值来自多个文档,可以对分组数据执行各种操作以返回单个结果。
聚合操作包含三类:单一作用聚合、聚合管道、MapReduce。

  • 单一作用聚合

提供了对常见聚合过程的简单访问,操作都从单个集合聚合文档。MongoDB 提供 db.collection.estimatedDocumentCount()db.collection.countDocument()db.collection.distinct()这类单一作用的聚合函数。 所有这些操作都聚合来自单个集合的文档。虽然这些操作提供了对公共聚合过程的简单访问,但它们缺乏聚合管道和 map-Reduce 的灵活性和功能。

  • 聚合管道

是一个数据聚合的框架,模型基于数据处理流水线的概念。文档进入多级管道,将文档转换为聚合结果。

  • MapReduce

操作具有两个阶段:处理每个文档并向每个输入文档发射一个或多个对象的 map 阶段,以及 reduce 组合 map 操作的输出阶段。从 MongoDB 5.0 开始,map-reduce 操作已被弃用。聚合管道比 map-reduce 操作提供更好的性能和可用性。

MongoDB 6.0 在原有聚合功能的基础上,推出了如下新特性以及优化项:

  • 分片集群实例支持 $lookup 和 $graphLookup。
  • 改进 $lookup 对 JOINS 的支持。
  • 改进 $graphLookup 对图遍历的支持。
  • 提升 $lookup 性能,部分场景中性能提升可达百倍。

聚合管道

MongoDB 聚合框架(Aggregation Framework)是一个计算框架,它可以:

  • 作用在一个或几个集合上;
  • 对集合中的数据进行的一系列运算;
  • 将这些数据转化为期望的形式;

从效果而言,聚合框架相当于 SQL 查询中的 GROUP BY、 LEFT OUTER JOIN 、 AS 等。

管道(Pipeline)和阶段(Stage)

整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的, 每个管道:

  • 接受一系列文档(原始数据);
  • 每个阶段对这些文档进行一系列运算;
  • 结果文档输出给下一个阶段;

通过将多个操作符组合到聚合管道中,用户可以构建出足够复杂的数据处理管道以提取数据并进行分析。
image.png
聚合管道操作语法:

pipeline = [$stage1, $stage2, ...$stageN];
db.collection.aggregate(pipeline, {options})
  • pipelines

组数聚合阶段。除 o u t 、 out、 outMerge 和 $geonear 阶段之外,每个阶段都可以在管道中出现多次。

  • options

可选,聚合操作的其他参数。包含:查询计划、是否使用临时文件、 游标、最大操作时间、读写策略、强制索引等等。

图示说明:
image.png

常用的聚合阶段运算符

聚合管道包含非常丰富的聚合阶段,下面是最常用的聚合阶段。

阶段运算符描述SQL等价运算符
$match筛选条件WHERE
$project投影AS
$lookup左外连接LEFT OUTER JOIN
$sort排序ORDER BY
$group分组GROUP BY
s k i p / skip/ skip/limit分页
$unwind展开数组
$graphLookup图搜索
f a c e t / facet/ facet/bucket分面搜索

参考文档:https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/

聚合表达式:

# 获取字段信息
$<field> :用 $ 指示字段路径
$<field>.<sub field> :使用 $ 和 . 来指示内嵌文档的路径

常量表达式:

$literal :<value> :指示常量 <value>

系统变量表达式:

$$<variable>  使用 $$ 指示系统变量
$$CURRENT 指示管道中当前操作的文档

测试数据脚本:

var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){var typeIdx = Math.floor(Math.random()*types.length);var tagIdx = Math.floor(Math.random()*tags.length);var tagIdx2 = Math.floor(Math.random()*tags.length);var favCount = Math.floor(Math.random()*100);var username = "xx00"+Math.floor(Math.random()*10);var age = 20 + Math.floor(Math.random()*15);var book = {title: "book-"+i,type: types[typeIdx],tag: [tags[tagIdx],tags[tagIdx2]],favCount: favCount,author: {name:username,age:age}};books.push(book)
}
db.books.insertMany(books);

SQL to Aggregation Mapping Chart:https://www.mongodb.com/docs/manual/reference/sql-aggregation-comparison/

下面详细说明。

$project

投影操作, 将原始字段投影成指定名称, 如将集合中的 title 投影成 name:

db.books.aggregate([{$project:{name:"$title"}}])

$project 可以灵活控制输出文档的格式,也可以剔除不需要的字段:

db.books.aggregate([{$project:{name:"$title",_id:0,type:1,author:1}}])

从嵌套文档中排除字段:

db.books.aggregate([{$project:{name:"$title",_id:0,type:1,"author.name":1}}
])

或者

db.books.aggregate([{$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])

$match

m a t c h 用于对文档进行筛选,之后可以在得到的文档子集上做聚合, match 用于对文档进行筛选,之后可以在得到的文档子集上做聚合, match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,match 可以使用除了地理空间之外的所有常规查询操作符,在实际应用中尽可能将 $match 放在管道的前面位置。这样有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果在投射和分组之前执行 $match,查询可以使用索引。

db.books.aggregate([{$match:{type:"technology"}}])

筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段,这样可以减少后续管道操作符要操作的文档数,提升效率。

db.books.aggregate([{$match:{type:"technology"}},{$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])

$count

计数并返回与查询匹配的结果数。

db.books.aggregate([{$match:{type:"technology"}},{$count: "type_count"}
])

说明:
m a t c h 阶段筛选出 t y p e 匹配 t e c h n o l o g y 的文档,并传到下一阶段; < b r / > match 阶段筛选出 type 匹配 technology 的文档,并传到下一阶段;<br /> match阶段筛选出type匹配technology的文档,并传到下一阶段;<br/>count 阶段返回聚合管道中剩余文档的计数,并将该值分配给 type_count;

$group

按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个 _id 字段,该字段按键包含不同的组。
输出文档还可以包含计算字段,该字段保存由 $group 的 _id 字段分组的一些 accumulator 表达式的值。 $group不会输出具体的文档而只是统计信息。

{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }

_id 字段是必填的。但是,可以指定 _id 值为 null 来为整个输入文档计算累计值。
剩余的计算字段是可选的,并使用 运算符进行计算。
_id 和 表达式可以接受任何有效的表达式。

accumulator 操作符:

名称描述类比sql
$avg计算均值。avg
$first返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。limit 0,1
$last返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。-
$max根据分组,获取集合中所有文档对应值得最大值。max
$min根据分组,获取集合中所有文档对应值得最小值。min
$push将指定的表达式的值添加到一个数组中。-
$addToSet将表达式的值添加到一个集合中(无重复值,无序)。-
$sum计算总和。sum
$stdDevPop返回输入值的总体标准偏差(population standard deviation)。-
$stdDevSamp返回输入值的样本标准偏差(the sample standard deviation)。-

g r o u p 阶段的内存限制为 100 M 。默认情况下,如果 s t a g e 超过此限制, group 阶段的内存限制为 100M。默认情况下,如果 stage 超过此限制, group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group 将产生错误。但是,要允许处理大型数据集,请将 allowDiskUse 选项设置为 true 以启用 $group 操作以写入临时文件。
book 的数量,收藏总数和平均值:

db.books.aggregate([{$group:{_id:null,count:{$sum:1},pop:{$sum:"$favCount"},avg:{$avg:"$favCount"}}}
])

统计每个作者的 book 收藏总数:

db.books.aggregate([{$group:{_id:"$author.name",pop:{$sum:"$favCount"}}}
])

统计每个作者的每本 book 的收藏数:

db.books.aggregate([{$group:{_id:{name:"$author.name",title:"$title"},pop:{$sum:"$favCount"}}}
])

每个作者的 book 的 type 合集:

db.books.aggregate([{$group:{_id:"$author.name",types:{$addToSet:"$type"}}}
])

$unwind

可以将数组拆分为单独的文档。
v3.2+ 支持如下语法:

{$unwind:{# 要指定字段路径,在字段名称前加上$符并用引号括起来。path: <field path>,# 可选,一个新字段的名称用于存放元素的数组索引。该名称不能以$开头。includeArrayIndex: <string>,  # 可选,default: false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档preserveNullAndEmptyArrays: <boolean> } }

姓名为 xx006 的作者的 book 的 tag 数组拆分为多个文档:

db.books.aggregate([{$match:{"author.name":"xx006"}},{$unwind:"$tag"}
])db.books.aggregate([{$match:{"author.name":"xx006"}}
])

每个作者的 book 的 tag 合集:

db.books.aggregate([{$unwind:"$tag"},{$group:{_id:"$author.name",types:{$addToSet:"$tag"}}}
])

案例

示例数据:

db.books.insert([
{"title" : "book-51","type" : "technology","favCount" : 11,"tag":[],"author" : {"name" : "firechou","age" : 28}
},{"title" : "book-52","type" : "technology","favCount" : 15,"author" : {"name" : "firechou","age" : 28}
},{"title" : "book-53","type" : "technology","tag" : ["nosql","document"],"favCount" : 20,"author" : {"name" : "firechou","age" : 28}
}])

测试:

# 使用includeArrayIndex选项来输出数组元素的数组索引
db.books.aggregate([{$match:{"author.name":"firechou"}},{$unwind:{path:"$tag", includeArrayIndex: "arrayIndex"}}
])
# 使用preserveNullAndEmptyArrays选项在输出中包含缺少size字段,null或空数组的文档
db.books.aggregate([{$match:{"author.name":"firechou"}},{$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}
])

$limit

限制传递到管道中下一阶段的文档数。

db.books.aggregate([{$limit : 5 }
])

此操作仅返回管道传递给它的前 5 个文档。 $limit 对其传递的文档内容没有影响。
注意:当 $sort 在管道中的 l i m i t 之前立即出现时, limit 之前立即出现时, limit之前立即出现时,sort 操作只会在过程中维持前 n 个结果,其中 n 是指定的限制,而 MongoDB 只需要将 n 个项存储在内存中。

$skip

跳过进入 stage 的指定数量的文档,并将其余文档传递到管道中的下一个阶段。

db.books.aggregate([{$skip : 5 }
])

此操作将跳过管道传递给它的前 5 个文档。 $skip 对沿着管道传递的文档的内容没有影响。

$sort

对所有输入文档进行排序,并按排序顺序将它们返回到管道。

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }

要对字段进行排序,请将排序顺序设置为 1 或 -1,以分别指定升序或降序排序,如下例所示:

db.books.aggregate([{$sort : {favCount:-1,"author.age":1}}
])

$lookup

MongoDB 3.2 版本新增,主要用来实现多表关联查询,相当关系型数据库中多表关联查询。每个输入待处理的文档,经过 $lookup 阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名新 key )。数组列存放的数据是来自被 Join 集合的适配文档,如果没有,集合为空(即 为[ ])。
语法:

db.collection.aggregate([{$lookup: {from: "<collection to join>",localField: "<field from the input documents>",foreignField: "<field from the documents of the from collection>",as: "<output array field>"}})

参数说明:

from同一个数据库下等待被 Join 的集合。
localField源集合中的 match 值,如果输入的集合中,某文档没有 localField 这个Key(Field),在处理的过程中,会默认为此文档含有 localField:null 的键值对。
foreignField待 Join 的集合的 match 值,如果待 Join 的集合中,文档没有 foreignField 值,在处理的过程中,会默认为此文档含有 foreignField:null 的键值对。
as为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉。

注意:null = null 此为真。
其语法功能类似于下面的伪 SQL 语句:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *FROM <collection to join>WHERE <foreignField>= <collection.localField>);

案例

数据准备:


db.customer.insert({customerCode:1,name:"customer1",phone:"13112345678",address:"test1"})
db.customer.insert({customerCode:2,name:"customer2",phone:"13112345679",address:"test2"})db.order.insert({orderId:1,orderCode:"order001",customerCode:1,price:200})
db.order.insert({orderId:2,orderCode:"order002",customerCode:2,price:400})db.orderItem.insert({itemId:1,productName:"apples",qutity:2,orderId:1})
db.orderItem.insert({itemId:2,productName:"oranges",qutity:2,orderId:1})
db.orderItem.insert({itemId:3,productName:"mangoes",qutity:2,orderId:1})
db.orderItem.insert({itemId:4,productName:"apples",qutity:2,orderId:2})
db.orderItem.insert({itemId:5,productName:"oranges",qutity:2,orderId:2})
db.orderItem.insert({itemId:6,productName:"mangoes",qutity:2,orderId:2})

关联查询:

db.customer.aggregate([        {$lookup: {from: "order",localField: "customerCode",foreignField: "customerCode",as: "customerOrder"}} 
])db.order.aggregate([{$lookup: {from: "customer",localField: "customerCode",foreignField: "customerCode",as: "curstomer"}},{$lookup: {from: "orderItem",localField: "orderId",foreignField: "orderId",as: "orderItem"}}
])

聚合操作案例 1

统计每个分类的 book 文档数量:

db.books.aggregate([{$group:{_id:"$type",total:{$sum:1}}},{$sort:{total:-1}}
])

标签的热度排行,标签的热度则按其关联 book 文档的收藏数(favCount)来计算:

db.books.aggregate([{$match:{favCount:{$gt:0}}},{$unwind:"$tag"},{$group:{_id:"$tag",total:{$sum:"$favCount"}}},{$sort:{total:-1}}
])

说明:

$match 阶段:用于过滤 favCount=0 的文档。
$unwind 阶段:用于将标签数组进行展开,这样一个包含 3 个标签的文档会被拆解为 3 个条目。
g r o u p 阶段:对拆解后的文档进行分组计算, ‘ group 阶段:对拆解后的文档进行分组计算,` group阶段:对拆解后的文档进行分组计算,sum:“$favCount”`表示按 favCount 字段进行累加。
$sort 阶段:接收分组计算的输出,按 total 得分进行排序。

统计 book 文档收藏数 [0,10),[10,60),[60,80),[80,100),[100,+∞):

db.books.aggregate([{$bucket:{groupBy:"$favCount",boundaries:[0,10,60,80,100],default:"other",output:{"count":{$sum:1}}}
}])

聚合操作案例 2

导入邮政编码数据集

数据地址:https://media.mongodb.org/zips.json

下载完成后,使用 mongoimport 工具导入数据:

mongoimport -h 192.168.65.174 -d test -u firechou -p firechou --authenticationDatabase=admin -c zips --file /user/local/mongodb/import/zips.json 

h,–host:代表远程连接的数据库地址,默认连接本地 Mongo 数据库;
–port:代表远程连接的数据库的端口,默认连接的远程端口 27017;
-u,–username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;
-p,–password:代表连接数据库的账号对应的密码;
-d,–db:代表连接的数据库;
-c,–collection:代表连接数据库中的集合;
-f, --fields:代表导入集合中的字段;
–type:代表导入的文件类型,包括 csv 和 json, tsv 文件,默认 json 格式;
–file:导入的文件名称;
–headerline:导入 csv 文件时,指明第一行是列名,不需要导入;

返回人口超过 1000 万的州:

db.zips.aggregate( [{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },{ $match: { totalPop: { $gte: 10*1000*1000 } } }
] )

这个聚合操作的等价 SQL 是:

SELECT state, SUM(pop) AS totalPop
FROM zips
GROUP BY state
HAVING totalPop >= (10*1000*1000)

返回各州平均城市人口:

db.zips.aggregate( [{ $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" } } },{ $group: { _id: "$_id.state", avgCityPop: { $avg: "$cityPop" } } }
] )

按州返回最大和最小的城市:

db.zips.aggregate( [{ $group:{_id: { state: "$state", city: "$city" },pop: { $sum: "$pop" }}},{ $sort: { pop: 1 } },{ $group:{_id : "$_id.state",biggestCity:  { $last: "$_id.city" },biggestPop:   { $last: "$pop" },smallestCity: { $first: "$_id.city" },smallestPop:  { $first: "$pop" }}},{ $project:{ _id: 0,state: "$_id",biggestCity:  { name: "$biggestCity",  pop: "$biggestPop" },smallestCity: { name: "$smallestCity", pop: "$smallestPop" }}}
] )

聚合优化

https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/

聚合优化的三大目标:

  • 尽可能利用索引完成搜索和排序
  • 尽早尽多减少数据量
  • 尽可能减少执行步骤

image.png

执行顺序

m a t c h / match/ match/sort vs p r o j e c t / project/ project/addFields:
为了使查询能够命中索引, m a t c h / match/ match/sort 步骤需要在最前面,该原则适用于 MongoDB <= 3.4。MongoDB 3.6 开始具备一定的自动优化能力。

$project + s k i p / skip/ skip/limit:
s k i p / skip/ skip/limit 应该尽可能放在 $project 之前,减少 $project 的工作量。3.6 开始自动完成这个优化。
image.png

内存排序

在没有索引支持的情况下,MongoDB 最多只支持使用 100MB 内存进行排序。假设总共可用内存为 16GB,一个请求最多可以使用 100MB 内存排序,总共可以有 16000 / 100 = 160 个请求同时执行。
内存排序消耗的不仅是内存,还有大量CPU。

方案一:$sort + $limit
只排Top N ,只要 N 条记录总和不超过 100MB 即可。

方案二:{allowDiskUse: true}
使用磁盘作为交换空间完成全量,超出 100MB 部分与磁盘交换排序。

方案三:索引排序
使用索引完成排序,没有内存限制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/327371.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

shell编程-分支语句和循环结构

流控制: •在一个shell脚本中的命令执行顺序称作脚本的流。大多数脚本会根据一个或多个条件来改变它们的流。 •流控制命令:能让脚本的流根据条件而改变的命令称为条件流控制命令 •exit语句:退出程序的执行&#xff0c;并返回一个返回码&#xff0c;返回码为0正常退出&#…

智慧校园全空间三维电子沙盘系统

一、概述 易图讯科技&#xff08;www.3dgis.top&#xff09;采用大数据、云计算、虚拟现实、物联网、AI等先进技术&#xff0c;自主可控高性能WebGIS可视化引擎&#xff0c;支持多用户客户端通过网络请求访问服务器地图和专题数据&#xff0c;提供地理信息数据、专题数据的并发…

真空引水罐 虹吸抽水机 负压虹吸罐 农业灌溉工作原理动画介绍

​ 1&#xff1a;真空引水罐虹吸抽水机虹吸罐介绍 真空引水罐是一种水泵吸水设备&#xff0c;也被称为真空罐、吸水罐或自动引水装置。它是一个密封的罐体&#xff0c;被串联在泵前的吸水管上&#xff0c;能够使水泵的吸水口从负压吸水变为正压吸水。使用真空引水罐可以节省真…

探索LinkedIn:使用TypeScript和jsdom库的高级内容下载器

概述 LinkedIn是一个专业的社交网络平台&#xff0c;拥有超过7亿的用户和数以亿计的职位、公司和教育机构的信息。对于数据分析师、市场营销人员、招聘人员和其他对LinkedIn数据感兴趣的人来说&#xff0c;能够从LinkedIn上获取和分析这些信息是非常有价值的。 因此&#xff0…

Apple M2 Pro芯片 + docker-compose up + mysql、elasticsearch pull失败问题的解法

背景 &#xff08;1&#xff09;从github上git clone了一个基于Spring Boot的Java项目&#xff0c;查看readme&#xff0c;发现要在项目的根目录下&#xff0c;执行“docker-compose up”。&#xff08;2&#xff09;执行“docker-compose up”的前提是&#xff0c;在macos上要…

WEB 3D技术 three.js 顶点交换

本文 我们来说 顶点的转换 其实就是 我们所有顶点的位置发生转变 我们整个物体的位置也会随之转变 这里 我们编写代码如下 import ./style.css import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.j…

UE4.27.2 网页串流

1、和Unity串流一样安装Node.js 下载地址https://nodejs.org/ 2、下载安装Epic Games启动程序https://www.unrealengine.com/zh-CN/download 3、安装UE4.7.2 4、这里就不安装像素流送演示&#xff0c;选个别的然后创建工程 5、启用PixelStreaming插件 6、设置额外启动参数&am…

基于web3+solidity的众筹项目

基本配置&#xff1a;node、npm、yarn&#xff0c;安装ganache&#xff0c;chrome&#xff0c;chrome安装插件MetaMask&#xff0c; 主要功能&#xff1a;目的是实现一个简单的众筹平台&#xff0c;允许用户发起筹款项目、捐款、提出使用资金请求以及证明人证明。 部分合约&…

大语言模型的幻觉:解析、成因及解决方法

目录 前言1 大语言模型的幻觉现象解析1.1 输入冲突幻觉&#xff08;Input-conflicting&#xff09;1.2 上下文冲突幻觉&#xff08;Context-conflicting&#xff09;1.3 事实冲突幻觉&#xff08;Fact-conflicting&#xff09; 2 幻觉产生的原因2.1 数据偏差和模型缺陷2.2 知识…

基于平衡优化器算法优化的Elman神经网络数据预测 - 附代码

基于平衡优化器算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于平衡优化器算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于平衡优化器优化的Elman网络5.测试结果6.参考文献7.Matlab代码…

odoo16 销售模块易错的几个操作

odoo16 销售模块易错的几个操作 据168Report调研团队最新报告“全球定制服装市场报告2023-2029”显示&#xff0c;预计2029年全球定制服装市场规模将达到1082.4亿美元&#xff0c;未来几年年复合增长率CAGR为7.8%。一个普通定制的小皮袄竟月销二十多万件&#xff0c;比我们做定…

静态网页设计——奥迪官网(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 使用技术&#xff1a;HTMLCSSJS 主要内容&#xff1a;①网站栏目设计说明。 网站为奥迪官网&#xff0c;由奥迪首页、奥迪车型&#xff08;具体车型介绍&#xff09;、…