概述:
众所周知,aggregate是mongodb非常强大的工具之一,之所以强大是因为它可以像乐高一样随意摆放各种组件(当然还是要遵守一定规则的)
注:aggregation就是单线程的,每个请求每个片一个线程。
简介:
下面我们就对aggregate的pipeline进行一一解释;
汇总:
- 1. $match: 匹配查询条件,针对的是源集合,可多次使用;
- 2. $limit: 限制输出的结果集 ;
- 3. $lookup: left join其他集合,且两个集合必须在一个库下 ;
- 4. $group: 类似于关系型数据库的group by,其后也可以加上诸多操作,比如$sum,$max,$min
- 5. $unwind: 分裂数组,重组数据 ;
- 6.$project: 字段重命名,字段做运算,合并,选择性的增加或删除字段;
- 7.$sort: 排序, 类似 order by
- 8.$skip: 省略某些行,类似find()后面的skip()
- 9.$redact: document内容的判断和"修剪”;
- 10.$sample: 随机选取指定数量的document;
- 11.$out: 将结果集输出到指定collection中;
- 12.$indexStats: 统计collection中的索引使用信息;
- 13.$geoNear: 根据位置信息查找最近和最远的点
- 14.$bucket: 根据某一个字段范围条件分组,然后分组计算、收集其他字段值等操作(我的理解,比较复杂,3.4新功能)
- 15.$addFields: 输出从上一管道符接受到的所有字段,并添加新字段(3.4新特性,类似于$project)
- 16.$bucketAuto
- 17.$collStats: 返回一个集合的统计信息(3.4新特性)
- 18.$count: 计算上一个管道符结果集的文档数量
- 19.$facet:
- 20.$graphLookup
- 21.$listSessions
- 22.$listLocalSessions
- 23.$replaceRoot
- 24.$sortByCount
- 25.$currentOp
注:其中的某些stage可以在一个aggregate语句中重复使用,比如$match,$group,$limit;这样可以进行更细化的数据处理
数据集合
使用方法详述
下面就简单介绍一下各个组件的使用方法:
1、$project:
遍历文档的指定字段到下一个stage(阶段);
意思就是说可以过滤掉某些字段(包括过滤掉_id)、可以重命名字段、添加新的字段,重置字段的值
语法:
{ $project: { <specifications> } }
<specifications>的选项 | 描述 |
<field>: <1 or true> | 执行显示哪些字段 |
_id: <0 or false> | 过滤掉_id字段 |
<field>: <expression> | 添加新字段或者重置字段的值 |
举例:
2、$match:
查询需要的数据给到下一个stage;
如果放到aggregate开端的话,可以利用上索引,与find()中的query语法一致
语法:
{ $match: { <query> } } ---query中写什么呢?就是在find()中写的东东
举例:
3、$limit:
其实就是做一个行数的限制,类似top 10 *
语法:
{ $limit: <positive integer> }
举例:
4、$lookup:
left join同一库下的另一集合去过滤数据,
语法:
$lookup:
{
from: <collection to join>, ---left join后的那个集合名称
localField: <field from the input documents>, ---用于链接的源集合的字段名
foreignField: <field from the documents of the "from" collection>, ---left join后的集合的链接字段
as: <output array field> ---从另一集合链接过来的数据会存放在一个数组中,这是写该数组的名称
}
}
注:如果你想要使用源集合中的数组的某个元素作为链接字段,那么你就需要考虑使用$unwind了,什么?你还不懂$unwind是什么?那就继续向下看吧
举例:
5、$unwind:
分裂数组,重组数据(document)
语法:
{
$unwind:
{
path: <field path>, ---你想要分裂的数组名称
includeArrayIndex: <string>, ---新增加一个字段用于描述数组分裂后各个元素在原数组中的位置,此处写新增字段的名(可选参数)
preserveNullAndEmptyArrays: <boolean> ---默认情况下对没有此数组的行不予显示,但如果想显示则设置为true,默认是false(可选参数)
}}
举例说明:
数据结构如下
普通分裂之后:
如果还想加上后面那俩货(参数),那么:
6、$group
将字段进行分组,并执行相应的操作,比如sum,max,min,但其不能排序结果集
语法:
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
expression1有很多选项:
$sum
$avg
$first
$last
$max
$min
$push
$addToSet
$stdDevPop
$stdDevSamp
可能看语法有点迷糊哦,直接上实例
数据结构:
分组:
分组并计算:
(注:count字段后面的操作可以是其他,比如max,min,push等等)
多字段分组并计算
(注:class_id,age,count的这些字段名都可以随便写哦,不要拘泥于制度)
在group管道符操作时,如果处理数据超过了内存限制,则会报错(如下),须使用allowDiskUse:true参数
7、$redact:
这是一个非常难以理解的管道符,我算是服了,整了好久才算是明白些
根据字段所处的document结构的级别,对文档进行适合的“修剪”,它通常和判断语句if...else结合使用,可选值有3个:
1)$$ DESCEND:包含当前document级别的所有fields。当前级别字段的内嵌文档将会被继续检测。
2)$$ PRUNE:不包含当前文档或者内嵌文档级别的所有字段,不会继续检测此级别的其他字段,即使这些字段的内嵌文档持有相同的访问级别。
3)$$ KEEP:包含当前文档或内嵌文档级别的所有字段,不再继续检测此级别的其他字段,即使这些字段的内嵌文档中持有不同的访问级别。
其实说的啥意思呢?就是某个字段符合$cond中的条件后如何处理后续字段,
① $$DESCEND: 符合条件后其他字段也都显示出来,并且对那些数据类型是数组或者内嵌文档的字段继续进行一样的if判断一样的处理
② $$PRUNE: 对符合条件的文档中所有字段全部不显示,更不会深入数组或内嵌文档汇总进行if检测
③$$KEEP:对符合条件的文档中同一级别的字段显示出来,但不会深入到数组或者内嵌文档的字段中进行if...else判断
举例:
集合数据结构
对age字段进行条件判断
db.t6.aggregate([
{$redact:{
$cond:{ ---固定格式,无需理解
if:{$gte:["$age",20]}, ---判断条件,判断字段age>20的情况
then:"$$DESCEND", ---如果成立,则显示同级别的所有字段,并继续深入检测
else:"$$PRUNE" ---如果不成立,则同级别的所有字段全都不显示,且停止深入检测
}
}}
])
8、$sample:
从上一个stage中随机选取指定数量的document
语法:
{ $sample: { size: <positive integer> } } ---size后面直接指定数字,代表你想选取的随机文档数量
9、$out:
将aggregate的聚合结果集输出到指定collection中存起来,(注:$out必须放到管道符的最后一个)
其实在我看来类似于 临时表的形式,不过咱们这个生成的是一个真实的实体集合,不会随着session的结束而删除
语法:
{ $out: "<output-collection>" }
举例:
db.t1.aggregate([
{$sample:{size:2}},
{$out:"random_test"}
])
10、$sort
将结果集以某字段排序,只是排序不会改变任何document
语法:
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } } ---可以指定多个字段,1代表升序 -1代表降序,类似 ordery by field1 ASC ,field2 DESC
举例:
限制:默认,$sort阶段有100M内存的使用限制,超过此限制则会报错
allowDiskUse选项,执行命令后会在dbpath的路径下产生一个_tmp的文件夹,用于临时存放sort的数据
注:但是allowDiskUse也不仅限于sort使用,官网解释如下:
11、$indexStats
查询一个collection中每一个index的统计信息
语法:
{ $indexStats: { } } ---写一个空{}就可以了
举例:
{
"name" : "cdate_-1", ---索引名
"key" : {
"cdate" : -1 ---索引使用的具体字段
},
"host" : "hostname111:27027", ---mongod进程所在的主机名
"accesses" : {
"ops" : NumberLong("4"), ---索引被访问的次数,
"since" : ISODate("2017-07-07T02:36:58.440Z") ---mongod开始收集index统计信息的时间
}
},
注:ops 表示访问次数,但是不包括$match和mapReduce操作的访问,也不包括内部操作比如TTL索引的内部每60秒自动删除过期数据的索引访问;
mongod重启,或者索引被删除重建后,索引的统计信息将被重置
12、$skip
省略掉一定数量的document,其实这个命令很是简单,功能作用也都好理解,我就不过于分析解释了,相信大家都是聪明人,哈哈...
语法:
{ $skip: <positive integer> } ---skip后面写个整数就可以了
13、$geoNear
此命令主要是用于地理位置查找分析,查找距离某个位置最近和最远的点,这个目前我们也用不上,看着还挺复杂,所以偶就先不研究了,后续补充上,
14、$bucket:
原集合数据:
执行语句:
(根据price字段,按照[0,200),[200,400)范围划分组,其他范围值都为Other,之后根据分组进行计算)
groupBy: 分组字段,
boundaries: 分组范围,结果集会根据范围左边界值作为_id的值
default : 不在上述范围内的,_id 值为Other,也可定义为其他
output:进行计算,多多益善,比如计数,求和,将分组里的某字段组合成新数组
15、$autoBucket
16、$addFields
此管道符的意思其实就是接受上一管道符的所有字段之外,还可以额外通过表达式添加新字段,有点类似于$project,不过还是不太一样的,我们看实验。
原集合数据
>db.scores.find()
{"_id":1,"student":"Maya","homework":[10,5,10],"extraCredit":0}
{"_id":2,"student":"Ryan","homework":[5,6],"extraCredit":8}
计算homework字段的元素总和,并且与extraCredit再计算生成第二个新字段值
>db.scores.aggregate([
{
$addFields: {totalHomework:{$sum:"$homework"} }
},
{
$addFields: {totalScore:{$add:["$totalHomework","$extraCredit"]}}
}
])
结果
{"_id":1,"student":"Maya","homework":[10,5,10],"extraCredit":0,"totalHomework":25,"totalScore":25}
{"_id":2,"student":"Ryan","homework":[5,6],"extraCredit":8,"totalHomework":11,"totalScore":19}
注:$addFields管道符可以多次使用,如果新字段的名称与现有字段相同,那么会覆盖掉现有字段的值(也包括_id);
17、$collStats
3.4新特性,用来显示某个集合的统计信息,返回的信息类似于db.collection.stats()命令,但更丰富些
使用如下默认格式即可
db.t1.aggregate([
{$collStats: {
latencyStats: { histograms: true },
storageStats: {},
count: {}
}}
])
注:$collStats此管道符必须放到第一的位置,否则报错
18、$count
其实从字段意思也能看出来,就是计算文档数量,只不过是可以计算上一管道符的结果集的文档数量,其实$count的这个功能用$group+$project也能实现
注:$count之后写的是计算结果的字段名称,不能包含'$',以及点
例:
原有数据:
筛选条件后并统计
>db.t2.aggregate([
{$match:{score:{$gte:80}}},
{$count:"total_score"}
])
结果
{"total_score":4}
19、