聚合操作主要用于对数据的批量处理,将记录按条件分组以后,然后再进行一系列操作,例如,求最大值、最小值、平均值,求和等操作。聚合操作还能够对记录进行复杂的操作,主要用于数理统计和数据挖掘。在 MongoDB 中,聚合操作的输入是集合中的文档,输出可以是一个文档,也可以是多条文档。本文将详细介绍 MongoDB 数据库聚合
【count】
count 是最简单,最容易,也是最常用的聚合工具,返回集合中的文档数量
- db.collection_name.count()
【distinct】
distinct() 方法返回不重复的结果
聚合管道由阶段(Stage)组成,文档在一个阶段处理完毕后,聚合管道会把处理结果传到下一个阶段
聚合管道可以对文档进行过滤,查询出符合条件的文档;也可以对文档进行变换,改变文档的输出形式
每个阶段用阶段操作符(Stage Operators)定义,在每个阶段操作符中可以用表达式操作符(Expression Operators)计算总和、平均值、拼接分割字符串等相关操作,直到每个阶段进行完成,最终返回结果,返回的结果可以直接输出,也可以存储到集合中
【aggregate()】
MongoDB 中使用 aggregate() 方法来构建和使用聚合管道,基本语法如下
- db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
下图是官网实例
实例中,$match 用于获取 status = "A" 的记录,然后将符合条件的记录送到下一阶段 $group 中进行分组求和计算,最后返回 Results。其中,$match、$group 都是阶段操作符,而阶段 $group 中用到的 $sum 是表达式操作符
接下来,对阶段操作符和表达式操作符进行详解。将下列数据储存到 article 集合中,下面的实例将反复用到 article 集合
- db.article.insertMany([{
- "_id": ObjectId("58e1d2f0bb1bbc3245fa7570"),
- "title": "MongoDB Aggregate",
- "author": "huochai",
- "tags": ['Mongodb', 'Database', 'Query'],
- "pages": 5,
- "time": ISODate("2017-07-19T22:42:39.736Z")
- },
- {
- "_id": ObjectId("58e1d2f0bb1bbc3245fa7571"),
- "title": "MongoDB Index",
- "author": "huochai",
- "tags": ['Mongodb', 'Index', 'Query'],
- "pages": 3,
- "time": ISODate("2017-07-19T22:43:39.236Z")
- },
- {
- "_id": ObjectId("58e1d2f0bb1bbc3245fa7572"),
- "title": "MongoDB Query",
- "author": "match",
- "tags": ['Mongodb', 'Query'],
- "pages": 8,
- "time": ISODate("2017-07-19T22:44:56.276Z")
- }])
在 UNIX 命令中,shell 管道可以对某些输入执行操作,并将输出用作下一个命令的输入。 MongoDB 也在聚合框架中支持类似的概念。每一组输出可作为另一组文档的输入,并生成一组生成的文档 (或最终生成的 JSON 文档在管道的末尾)。这样就可以再次用于下一阶段等等。
以下是在聚合框架可能的阶段操作符
- $project - 用于从集合中选择一些特定字段$match - 这是一个过滤操作,因此可以减少作为下一阶段输入的文档数量。$group - 这是上面讨论的实际聚合。$sort - 排序文档。$skip - 通过这种方式,可以在给定数量的文档的文档列表中向前跳过。$limit - 限制从当前位置开始的给定数量的文档数量。$unwind - 用于展开正在使用数组的文档。使用数组时,数据是预先加入的,此操作将被撤销,以便再次单独使用文档。因此,在这个阶段,将增加下一阶段的文件数量。
【$project】
下面示例中把文档中 pages 字段的值都增加 10,并重命名成 newPages 字段,且不显示_id 字段
- db.article.aggregate([{
- $project: {}
- }])
【$match】
在 $match 中不能使用 $where 表达式操作符。如果 $match 位于管道的第一个阶段,可以利用索引来提高查询效率。如果 $match 中使用 $text 操作符的话,只能位于管道的第一阶段。$match 尽量出现在管道的最前面,过滤出需要的数据,在后续的阶段中可以提高效率
查询出文档中 pages 字段的值大于等于 5 的数据
【$group】
从 article 中得到每个 author 的文章数,并输入 author 和对应的文章数
【$sort】
让集合 article 以 pages 升序排列
【$limit】
返回集合 article 中前两条文档
【$skip】
跳过集合 article 中一条文档,输出剩下的文档
【$unwind】
$unwind 参数数组字段为空或不存在时,待处理的文档将会被忽略,该文档将不会有任何输出。$unwind 参数不是一个数组类型时,将会抛出异常。$unwind 所作的修改,只用于输出,不能改变原文档
把集合 article 中 title="MongoDB Aggregate" 的 tags 字段拆分
表达式操作符有很多操作类型,其中最常用的有布尔管道聚合操作、集合操作、比较聚合操作、算术聚合操作、字符串聚合操作、数组聚合操作、日期聚合操作、条件聚合操作、数据类型聚合操作等
【布尔】
- $and与$or或$not非
x>=10,并且 x<=30 的文档,返回 true
x>30,或者 x<20 的文档,返回 true
x>20 的文档,返回 true
【文氏图集合操作】
- $setEquals除了重复元素外,包括的元素相同$setIntersection交集$setUnion并集$setDifference只在前一集合出现,也就是后一个集合的补集$setIsSubset前一个集合是后一个集合的子集$anyElementTrue一个集合内,只要一个元素为真,则返回true $allElementsTrue一个集合内,所有的元素都为真,则返回true
集合 A 与集合 B,除了重复元素外,包括的元素相同,返回 true
返回集合 A 与集合 B 的交集
返回集合 A 与集合 B 的并集
返回只在集合 A 中出现的数据,或者说是集合 B 的补集
集合 A 是集合 B 的子集,则返回 true
只要一个为 true,则返回 true
全部为 true,返回 true
【比较操作】
- $cmp两个值相等返回0,前值大于后值返回1,前值小于后值返回 - 1 $eq是否相等$gt前值是否大于后值$gte前值是否大于等于后值$lt前值是否小于后值$lte前值是否小于等于后值$ne是否不相等
qty 与 250 相比较
qty 与 250 是否相等
qty 是否大于 250
qty 是否大于等于 250
qty 是否小于 250
qty 是否小于等于 250
qty 与 250 是否不相等
【算术运算】
- $abs绝对值$add和$ceil向上取整$divide除$expex $floor向下取整$ln自然对数$log对数$log10以10为底的对数$mod取模$multiply乘$pow指数$sqrt平方根$subtract减$trunc截掉小数取整
返回 start - end 后的绝对值
返回 start + end 的和
返回 start / end 的结果
返回 estart 的值
返回 logestart 的值
返回 logendstart 的值
返回 log10start 的值
返回 start mod end 的值
返回 start * end 的积
返回 startend
返回 start 的平方根
返回 start - end 的差值
返回 x 的向上取整值
返回 x 的向下取整值
返回 x 的截掉小数取整值
【字符串操作】
- $concat字符串连接$indexOfBytes子串位置 (字节) $indexOfCP子串位置 (字符) $split分割字符串$strLenBytes字节长度$strLenCP字符长度$strcasecmp字符串比较$substrBytes创建子串 (按字节) $substrCP创建子串 (按字符) $toLower小写$toUpper大写
返回 item 和 description 连接后的字符串
返回'foo'在 item 中第一次出现的位置 (字节)
返回'foo'在 item 中第一次出现的位置 (字符)
以'o'来分割 item
返回 item 的字节长度
返回 item 的字符长度
返回 item 与''foo" 比较后的结果
返回 item 在 0-3 字节位置的子串
返回 item 在 0-3 字符位置的子串
将 item 大写
【数组操作】
- $arrayElemAt返回指定数组索引中的元素$concatArrays数组连接$filter返回筛选后的数组$indexOfArray索引$isArray是否是数组$range创建数值数组$reverseArray反转数组$reduce对数组中的每个元素应用表达式,并将它们组合成一个值$size数组元素个数$slice子数组$zip合并数组$in返回一个布尔值,表示指定的值是否在数组中
返回索引为 0 的元素
将 name 与 favorites 数组合并
返回 item.price 大于等于 100 的 item
返回数字 2 在数组中的索引值
是否是数组
- {
- $isArray: ["hello"]
- }
- false {
- $isArray: [["hello", "world"]]
- }
- true
创建数值数组
- {
- $range: [0, 10, 2]
- } [0, 2, 4, 6, 8] {
- $range: [10, 0, -2]
- } [10, 8, 6, 4, 2] {
- $range: [0, 10, -2]
- } [] {
- $range: [0, 5]
- } [1, 2, 3, 4, 5]
返回反转的数组
- {
- $reverseArray: [1, 2, 3]
- } [3, 2, 1] {
- $reverseArray: {
- $slice: [["foo", "bar", "baz", "qux"], 1, 2]
- }
- } ["baz", "bar"] {
- $reverseArray: null
- }
- null {
- $reverseArray: []
- } [] {
- $reverseArray: [[1, 2, 3], [4, 5, 6]]
- } [[4, 5, 6], [1, 2, 3]]
对数组中的每个元素应用表达式,并将它们组合成一个值
- {
- $reduce: {
- input: [[3, 4], [5, 6]],
- initialValue: [1, 2],
- in:{
- $concatArrays: ["$$value", "$$this"]
- }
- }
- } [1, 2, 3, 4, 5, 6]
返回数组元素个数
返回子数组
- {
- $slice: [[1, 2, 3], 1, 1]
- } [2] {
- $slice: [[1, 2, 3], -2]
- } [2, 3] {
- $slice: [[1, 2, 3], 15, 2]
- } [] {
- $slice: [[1, 2, 3], -15, 2]
- } [1, 2]
返回一个布尔值,表示指定的值是否在数组中
- {
- $in: [2, [1, 2, 3]]
- }
- true {
- $in: ["abc", ["xyz", "abc"]]
- }
- true {
- $in: ["xy", ["xyz", "abc"]]
- }
- false {
- $in: [["a"], ["a"]]
- }
- false {
- $in: [["a"], [["a"]]]
- }
- true {
- $in: [/^a/, ["a"]]
- }
- false {
- $in: [/^a/, [/^a/]]
- }
- true
【日期操作】
- $dayOfYear日 (1 - 366) $dayOfMonth月 (1 - 23) $dayOfWeek星期(1(Sunday)到7(Saturday))$year年$month月 (1 - 12) $week周 (0 - 53) $hour时 (0 - 23) $minute分 (0 - 59) $second秒 (0 - 60) $millisecond毫秒 (0 - 999) $dateToString返回格式化字符串的日期$isoDayOfWeek以ISO 8601格式返回星期几$isoWeek以ISO 8601格式返回周号,范围从1到53 $isoWeekYear以ISO 8601格式返回年份编号
- db.a.aggregate([{
- $project: {
- year: {
- $year: "$date"
- },
- month: {
- $month: "$date"
- },
- day: {
- $dayOfMonth: "$date"
- },
- hour: {
- $hour: "$date"
- },
- minutes: {
- $minute: "$date"
- },
- seconds: {
- $second: "$date"
- },
- milliseconds: {
- $millisecond: "$date"
- },
- dayOfYear: {
- $dayOfYear: "$date"
- },
- dayOfWeek: {
- $dayOfWeek: "$date"
- },
- week: {
- $week: "$date"
- }
- }
- }])
【条件操作】
- $cond三元操作符$ifNull返回第一个表达式的非空结果或第二个表达式的结果$switch
- switch操作符
如果 qty>=250,返回 true
如果 qty 是空,则 result 返回 "是空的",否则 result=qty
switch 操作符示例
- {
- $switch: {
- branches: [{
- case:
- {
- $eq:
- [0, 5]
- },
- then: "equals"
- },
- {
- case:
- {
- $gt:
- [0, 5]
- },
- then: "greater than"
- }],
- default:
- "Did not match"
- }
- }
- "Did not match"
【优化】
默认情况下,整个集合作为聚合管道的输入,为了提高处理数据的效率,可以使用以下策略:
1、将 $match 和 $sort 放到管道的前面,可以给集合建立索引,来提高处理数据的效率
2、可以用 $match、$limit、$skip 对文档进行提前过滤,以减少后续处理文档的数量
3、当聚合管道执行命令时,MongoDB 也会对各个阶段自动进行优化,主要包括以下两种情况:
【1】$sort + $match 顺序优化。如果 $match 出现在 $sort 之后,优化器会自动把 $match 放到 $sort 前面
【2】$skip + $limit 顺序优化。如果 $skip 在 $limit 之后,优化器会把 $limit 移动到 $skip 的前面,移动后 $limit 的值等于原来的值加上 $skip 的值
【限制】
对聚合管道的限制主要表现在对返回结果大小和内存的限制
1、返回结果大小
聚合结果返回的是一个文档,不能超过 16M,从 MongoDB 2.6 版本以后,返回的结果可以是一个游标或者存储到集合中,返回的结果不受 16M 的限制。
2、内存
聚合管道的每个阶段最多只能用 100M 的内存,如果超过 100M,会报错,如果需要处理大数据,可以使用 allowDiskUse 选项,存储到磁盘上
来源: http://www.cnblogs.com/xiaohuochai/p/7204875.html