Skip to main content

MongoDB中的聚合操作简介

MongoDB中的聚合操作简介

00:00/00:00

聚合操作对数据库中的数据记录进行处理,并返回计算之后的结果。聚合操作从多个文档中获取相应的值,然后在对应的数据中执行各种操作来返回单一的结果。MongoDB提供了三种执行聚合的方式:聚合管道、map-reduce函数以及单一目的的聚合方法。

聚合管道

MongoDB的聚合管道是基于数据处理管道的概念进行构建的。文档会输入到一个多阶段的管道:将文档转化为一个聚合结果。

最基本的管道阶段提供了过滤(它的操作类似于查询)以及文档转换来修改输出文档的形式。

其它的管道操作提供了根据特定字段对文档进行分组及排序的工具以及对数组内容(包含文档数组)进行聚合的工具。此外,管道阶段可以使用操作符用于例如:计算平均值或连结字符串的任务。

管道中通过使用MongoDB中本地的操作来提供高效的数据聚合,是MongoDB中比较推荐的用于数据聚合的方法。

聚合管道也可以在复制集中进行操作。

聚合管道可以在某些阶段使用索引来提高它的性能。此外,聚合管道有一个内部的优化阶段。

概述

MongoDB的聚合管道由阶段组成。每个阶段在文档经过管道的时候对它们进行转换。管道阶段并不需要为每个输入文档都生成一个输出文档。例如,一些阶段有可能会生成新文档或者是过滤掉文档。管道阶段可以在管道中出现多次。

MongoDB在mongo shell中提供了db.collections.aggregate()方法以及aggregate命令用于聚合管道。

管道表达式

一些管道阶段将管道表达式作为操作对象。管道表达式指定了运用到输入文档上的转化。表达式由一个文档结构可以包含其它表达式。

管道表达式只可以对当前处于管道中的文档进行操作,不能引用其它文旦中的数据:表达书操作提供文档的内存中转化。

一般说来,表达式是无状态的并且只有在被聚合进程看到的时候才会进行操作(累加表达式除外)。

$group阶段使用的累加操作符,会在管道的文档进程中维护它们的状态(例如,总和、最大值、最小值和相关数据)。

3.2中的变化:一些累加操作符也可以在$project阶段使用;然而,在该阶段使用的时候,累加操作符不会维护文档之间的状态。

聚合管道操作

在MongoDB中,aggregate命令在单个集合中进行操作,逻辑上会将整个集合传入聚合管道中。为了优化这个操作,如果可能的话,使用下面的策略来避免扫描整个文档。

管道操作符和索引

$match$sort管道操作符都可以在管道的开始阶段利用索引的优势。

从2.4版本开始,$geoNear管道操作符利用了地理空间索引的优势。当使用该操作符时,$geoNear管道操作必须出现在集合管道的第一个阶段。

3.2版本中进行的修改:从MongoDB 3.2开始,索引可以覆盖到聚合管道。而在MongoDB 2.6和3.0中,即使管道中使用了索引,索引也不能覆盖聚合管道,聚合仍然需要访问真实的文档。

早期过滤

如果我们的聚合操作只需要集合中数据的子集,可以使用$match$limit以及$skip阶段来限制最开始进入管道的文档。当被放置在管道的开始阶段时,$match操作使用合适的索引来扫描集合中匹配的文档。

管道开始部分,在$match管道阶段之后使用一个$sort阶段在逻辑上等同于一个使用了排序的简单查询,并且也可以使用索引。可能的话,在管道的开始出使用了一个$match操作符。

聚合管道操作符

本文主要介绍操作符的定义和功能,具体的示例都可以在官网上找到。其实大部分情况下都只会使用到一部分,大家只要有针对性地去找就好了。官网链接

阶段操作符

db.collection.aggregate方法中,pipeline阶段以数组的形式出现。文档顺序进入各个阶段。

  1. db.collection.aggregate( [ { <stage> }, ... ] )
名称 描述
$collStats 返回集合或者视图的统计信息
$project 在数据流中重塑每个文档,例如:增加新字段或者删除已有字段,对于每一个输入文档,输出一个文档。
$match 对文档流进行过滤,只允许匹配的文档不做任何修改地输入到下一个管道阶段。$match使用标准的MongoDB查询,对于每个输入文档,要么输出一个文档(匹配),要么输出0个文档(未匹配)。
redact 通过限制每个文档的内容基于存储在文档本身的信息对流中的每个文档进行重塑。结合了$project$match的功能,可以被用于实现字段级别的规约。对于每个文档,输入一个或零个文档。
$limit 当指定返回数为n时,将前n个文档不做任何修改地传入到管道中。对于每个输入文档,要么输入一个文档(在前n个文档中),要么输出零个文档(不在前n个文档中)。
$skip 当指定n为跳过数时,跳过前n个文档,将剩下的文档不做任何修改地传入到管道中。对于每个输入文档,要么输入零个文档(在前n个文档中),要么输出一个文档(不在前n个文档中)。
$unwind 将输入文档中的数组字段进行重组,并将每个元素都输出到文档中。每个输出文档将数组替换为一个元素值。对于每个输入文档,输出n个文档。其中,n是数组元素的个数,对于空数组而言它的值为零。
$group 根据特定的标识表达式将输入文档进行分组,然后在指定了累计操作符的情况下,对每个组执行累计表达式。处理所有的输入文档,然后对每个不同的组输出一个文档。在指定了累计字段的情况下,输出的文档只包含标识字段以及累计的字段。
$sample 从输入中随机选取指定数目的文档
$sort 根据指定的排序键值对文档流进行排序。只有顺序会改变,而文档维持不变。对于每个输入文档。输出一个文档。
$geoNear 基于到某个地理坐标的距离返回有序的文档流。针对地理空间数据组合了$match```、limit“操作。输出的文档中包括一个额外的距离字段,包括一个位置标识字段。
$lookup 相同数据库上的另一个集合执行一个左外连接操作,目的是为了从joined集合的文档进行过滤用于处理。
$out 将聚合管道的结果文档写入到集合中。如果需要使用$out阶段的话,必须放在管道的最后一个阶段。
$indexStats 返回集合中每个索引的详细统计。
$facet 在一个阶段对相同的输入文档集执行多个聚合管道。启用多面聚合的创建能够在一个阶段中从多个维度或方面获取数据的特征。
$bucket 基于特定的表达式和桶范围,将输入的文档分组,称为:桶。
$bucketAuto 基于特定的表达式和桶范围,将输入的文档分组,称为:桶。桶的范围基于将文档平均划分到特定数目的桶中自动决定。
$sortByCount 基于特定表达式的值将输入文档进行分组,然后计算每个单一组中的文档数。
$addFields 向新文档中加入新字段。将包含输入文旦中的所有已有字段以及新添加的字段输出到文档。
$replaceRoot 将特定的嵌入文档替换为某个文档。这个操作替换输入文旦中的所有已存在的字段,包括_id字段。指定输入文档中的嵌入文档,可以将嵌入文档提高到最高级。
$count 返回聚合管道中这个阶段的文档数目。
$graphLookup 在集合上执行一个递归检索。对于每一个输出文档,增加一个新的数组字段,包含对该文档递归检索的遍历结果。

表达式操作符

这些表达式操作符可以用于聚合管道中构建表达式,

表达式操作符与需要传递参数的函数类似。一般说来,这些表达式将一个参数数组作为输入,有以下形式:

  1. { <operator>: [ <argument1>, <argument2> ... ] }

如果操作符接受一个单一参数,我们可以忽略外面用于指定参数列表的数组:

  1. { <operator>: <argument> }

为了避免解析的模糊性,如果参数本来就是一个数组,那么我们就必须将这个数组放在$literal表达式中,或者保留指定参数列表的外部数组符号。

布尔操作符

布尔表达式将它们的参数表达式作为布尔型,然后返回一个布尔值作为结果。

除了false布尔值,布尔表达式在下列情况下也会将值评估为false值:null0以及undefined值。布尔表达式将其它所有值都评估为true,包括非0的数值和数组。

名称 描述
$and 当且仅当所有表达式的值均为true时才会返回true。接收任意数目的参数表达式。
$or 任一表达式的值为true时均会返回true。接收任意数目的参数表达式。
$not 返回参数表达式想法的布尔值。接收单个参数表达式。

set操作符

set操作符在数组上执行set操作,将数组看做集合。set操作符会忽略每个输入文档中重复的条目以及元素的顺序。

如果set操作返回一个集合,那么这个操作就是过滤掉结果中的重复值以输出一个只包含单一条目的数组。输出数组中的元素顺序不能确定。

如果一个集合包含一个嵌套数组元素,set操作符不会降级到签到数组,只会评估最顶层的数组。

名称 描述
$setEquals 输入集合中拥有相同的元素时,返回true。接收2个或者更多参数表达式。
$setIntersection 返回在所有输入集合中均存在的元素集合。接收任意数目的参数表达式。
$setUnion 返回出现在任何输入集中的元素集合。接收任意数目的参数表达式。
$setDifference 返回在第一个集合中出现但是并未出现在第二个集合中的元素集合;例如,计算第二个集合相对于第一个集合的相对补集。只接受两个参数表达式。
$setIsSubset 当第一个集合中的所有元素都出现在第二个集合中时返回true,包括第一个集合等于第二个集合的情况(不是一个严格的子集)。只接受两个参数表达式。
$anyElementTrue 如果一个集合中的任一元素被评估为true,就返回false;否则的话,返回false。接收单个的参数表达式。
$allElementTrue 如果一个集合中的所有元素被评估为false,就返回true;否则的话,返回false。接收单个的参数表达式。

比较操作符

除了$cmp返回一个数字外,比较操作符均返回一个布尔值。

比较操作符接收两个参数表达式,然后同时比较只和类型,针对不同类型的值使用特定的BSON比较顺序

名称 描述
$cmp 如果两个值相等,返回0,如果第一个值大于第二个值,返回1,如果第一个值小于第二个值,返回-1
$eq 当值相等时,返回true
$gt 当第一个值大于第二个值时,返回true
$gte 当第一个值大于等于第二个值时,返回true
lt 当第一个值少于第二个值时,返回true
lte 当第一个值小于等于第二个值时,返回true
ne 当值相等时,返回true

算术操作符

算术表达式在数值上执行算术操作。一些算术表达式也可以支持日期算术。

名称 描述
$abs 返回数值的绝对值
$add 将数字进行求和返回和,或者将日期和数组相加返回一个新日期。如果将数组和日期相加,将数组当做毫秒。接收任何数目的参数表达式,但是每个表达式最多只能处理一个日期。
$ceil 返回大于或等于指定数字的最小整数值
$divide 返回第一个数字初一第二个数字的结果。接收两个参数表达式。
$exp e的指定值指数。
$floor 返回小于或等于指定数组的最大整数。
$ln 计算某个数字的自然对数。
$log 计算某个数字指定基数的对数值。
$log10 计算某个数字以10为底的对数值。
$mod 返回第一个数字处于第二个数的余数。接收两个参数表达式。
$multiply 将数字相乘返回乘机。接收任意数目的参数表达式。
$pow 求某个数的特定指数。
$sqrt 计算平方根。
$substract 返回第一个数减去第二个值的结果。如果两个值都是数组,返回差值。如果两个数的手日期,返回以毫秒为单位的差值。如果一个值是日期,一个值时以毫秒为单位的数字,返回结果日期。接收两个参数表达式。如果两个值中一个是日期一个是数字,那么僵日期参数放在前面,因为一个数字减去一个日期并没有什么意义。
$trunc 将一个数截断为整数,

字符串操作符

除了$concat之外,其它所有字符创表达式只针对ASCII字符的字符串进行操作。

concat行为不依赖于使用的字符。

名称 描述
$concat 连接任意数目的字符串
$indexOfBytes 检索一个字符创是否包括该子字符串,并且返回第一次出现的UTF-8字节索引。如果没有发现该字符串,返回-1
$indexOfCP 检索一个字符创是否包括该子字符串,并且返回第一次出现的UTF-8代码点(code point)索引。如果没有发现该字符串,返回-1
$split 将一个字符串基于一个分隔符分割为子字符串。返回字符串的数组。如果没有在字符中发现该分隔符,返回包含原始字符串的数组。
$strLenBytes 返回字符串中UTF-8编码的字节数。
$strLenCP 返回字符串中UTF-8的代码点(code point)数。
$strcasecmp 在大小写敏感的字符串中进行比较:如果两个字符串相等,返回0;如果第一个字符串的值大于第二个字符串返回1;否则返回-1。
$substr 已过期,。使用$substrBytes或者$substrCP代替。
substrBytes 返回一个字符串的子字符串。以字符串中指定的UTF-8字节索引(0开始)的字符开始,返回指定数目的字节数。
substrCP 返回一个字符串的子字符串。以字符串中指定的UTF-8代码点(CP)(0开始)的字符开始,返回指定数目的代码点数。
toLower 将字符串转化为小写。接收单个的参数表达式。
toUpper 将字符串转化为大写。接收单个的参数表达式。

文本检索操作符

名称 描述
$meta 读取文本检索元数据。

数组操作符

名称 描述
$arrayEleAt 返回指定数组索引的元素。
$concatArrays 将数组连结,返回被连结的数组。
$filter 选择数组的子集返回一个只包含匹配过滤条件元素的数组。
$indexOfArray 检索一个数组判断特定的值是否存在,并且返回该元素第一次出现的数组索引。如果不存在该子数组,返回-1
$isArray 判断操作数是否为数组,返回布尔值。
$range 根据用户定义的输入,输出一个包含一系列整数的数组。(例如,1,2,…,20)
$reverseArray 以逆序排列的元素数组。
$reduce 对数组中的每个元素都执行一个表达式,并且将它们组合为一个单一的值。
$size 返回数组中元素的个数。接收单一表达式为参数。
$slice 返回数组的子集。
$zip 将两个列表合并。
$in 返回一个布尔值,标识数组中是否包含特定元素。

变量操作符

名称 描述
map 对数组中的每个元素都执行一个子表达式,按照顺序返回结果值的数组。接收命名参数。
let 定义自表达式范围内的变量,返回子表达式的结果。接收命名参数。

literal操作符

名称 描述
$literal 返回一个不进行解析的值。用于那些有可能会被聚合管道解析为表达式的值。例如,对一个以$开头的字符串使用$literal表达式以避免将其解析为一个字段路径。

日期操作符

名称 描述
$dayOfYear 返回某个日期为该年份的第多少天,为1-366之间的数字(只有闰年才可能返回366)。
$dayOfMonth 返回某个日期为该月份的第多少天,为0-31之间的数字。
$dayOfWeek 返回某个日期为该星期的第多少天,为1(星期天)-7(星期六)之间的数字。
$year 返回某个日期的年份(例如,2014)。
$month 返回某个日期的月份,为1(1月)到12(12月)之间的数字。
$week 返回某个日期为该年份的第几周,为0(在该年的第一个星期天之前部分日期)到53(闰年)之间的数字。
$hour 返回某个日期为该日的第几个小时,为0到23之间的数字。
$minute 返回某个日期为该小时的第几分钟,为0到59之间的数字。
$second 返回某个日期为该分钟的第几秒,为0到50(闰秒)之间的数字。
$millisecond 返回某个日期为该秒的第几毫秒,为0到999之间的数字。
$dateToString 返回日期为一个何世华的字符串
$isoDayOfWeek 返回ISO 8601格式的第几天数目,为1(星期一)到7(星期日)之间的数字。
$isoWeek 返回ISO 8601格式的星期数,从1到53,星期数从1开始计数,其中第一周(周一到周日)为包含该年第一个周二的那一周。
$isoWeekYear 返回ISO 8601的年数。该年从第一周(ISO 8601)的周一开始,以最后一周(ISO 8601)的星期天为结束。

条件表达式

名称 描述
$cond 一个三维的操作符:使用一个判别表达式,根据结果返回两个表达式中的一个表达式的值。接收三个以一定顺序排列的三个表达式或者三个命名参数。
$ifNull 返回第一个表达式的非空结果或者在第一个表达式为空时第二个表达式的结果。空结果包括未定义的值或缺失字段的实力。接收两个表达式作为参数。第二个表达式的结果可以为空。
$swich 判断一系列的case表达式。当发现有一个表达式的值为true时,$swich就会执行一个特定的表达式,并且会终止控制流程。

数据类型表达式

名称 描述
$type 返回该字段的BSON数据类型。

计数器

从3.2版本开始:一些计数器可以在$project阶段使用。在之前的版本中,计数器只能用于$group阶段。
$group阶段中使用计数器时,会在管道中处理文档的过程中维护它们的状态(例如,求和、最大值、最小值以及相关数据等)。
$group阶段使用时,计数器将一个单一的表达式作为输入,对每个输入文档都进行一次计算,然后维护具有相同分组键值的文档组阶段。
而在$project阶段使用时,计数器并不会维护它们的状态。具体使用过程中,计数器要么使用一个单一参数作为输入,要么使用多个参数作为输入。

名称 描述
$sum 返回数值的和,忽略掉非数字的值。从3.2版本开始,可以同时用于$group$project阶段。
$avg 返回数值的平均值,忽略掉非数字的值。从3.2版本开始,可以同时用于$group`$project阶段`。
$first 返回每个组别中的第一个文档的值。只有在文档处于一个定义好的顺序的时候才能定义该顺序。只可以在$group阶段使用。
$last 返回每个组别中的第最后文档的值。只有在文档处于一个定义好的顺序的时候才能定义该顺序。只可以在$group阶段使用。
$max 返回每个组别中最大的表达式值。从3.2版本开始,可以同时用于$group$project阶段。
$min 返回每个组别中最小的表达式值。从3.2版本开始,可以同时用于$group$project阶段。
$push 返回每个组别的表达式值数组。只可以在$group阶段使用。
$addToSet 返回每个组别的单一表达式值数组。没有定义数组元素的顺序。只可以在$group阶段使用。
$stdDevPop 返回输入值的总体标准差。从3.2版本开始,可以同时用于$group`$project阶段`。
$stdDevSamp 返回输入值的样本标准差。从3.2版本开始,可以同时用于$group`$project阶段`。

Code Point: (1)统一字符编码的编码空间的任何值,也就是从0到的整数。(2)某个字符在任何编码字符集中的值或位置。

Map-Reduce

MongoDB也提供了map-reduce操作执行聚合。一般说来,map-reduce操作有两个阶段:map阶段对每个文档进行处理,并且对每个输入文档生成一个或更多对象;而reduce阶段将map操作的输出进行组合。此外,map-reduce也可以有一个finalize阶段来对结果进行最后的修改(可选)。和其它聚合操作一样,map-reduce可以指定一个查询条件来选择输入的文旦以及对结果进行排序和限制返回结果数。

map-reduce使用自定义的JavaScript函数来进行map和reduce操作,以及可选的finalize操作。尽管与聚合管道相比,自定义的JavaScript提供了非常大的灵活性,一般说来,map-reduce并没有聚合管道高效并且会更加复杂。

map-reduce可以在分片集合上进行操作。map、reduce操作也可以输出到一个分片集合中。

注意:从MongoDB 3.2开始,一些mongo shell函数以及属性就无法在map-reduce中获取了。 同时,MongoDB 2.4也对多个JavaScript操作的运行提供了支持。 在MongoDB 2.4之前, JavaScript在单线程中执行,往往会触发map-reduce中的并发性问题。

单一目的的聚合操作

MongoDB也提供了db.collection.countdb.collection.distinc()的操作。

所有这些操作会从单一的集合中聚合文旦。尽管这些操作提供了对普通聚合操作的简单实用方式,但是它们缺乏聚合管道和map-reduce的灵活性和能力。

 

小结

本文主要详细介绍了MongoDB中最常用的聚合操作实现方式——集合管道中各种阶段和各种操作符的简单描述和主要功能。总的说来,聚合管道中基本上已经提供了基本数据分析过程中经常会使用到的一些功能了,大家只要按需了解即可。特别是MongoDB 3.4中引入的分桶统计的方法,非常实用。下周将会具体针对各个操作符的用法和示例进行详细的讲解,敬请期待。

文章的最后,附上一些昨天去香港玩的照片。大概花了一整天的时间,路线是:福田口岸->落马洲->黄大仙庙->香港科技大学->铜锣湾->中环->尖沙咀->维多利亚港->旺角->落马洲->福田口岸。一句话概括就是:风景很美,痛并快乐着。做的准备还是不够充分~以后有时间也可以做个总结(啦啦啦啦)。

 

打赏
微信扫一扫支付
微信logo微信扫一扫, 打赏作者吧~

mickey

记录生活,写给几十年后的自己。