MongoDB聚合操作2——聚合管道

MongoDB 中使用 db.COLLECTION_NAME.aggregate([{<stage>},...]) 方法来构建和使用聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。聚合管道的工作流程如下:

image
  • $match 用于获取 status = "A" 的记录,然后将符合条件的记录送到下一阶段
  • $group 中进行分组求和计算,最后返回 Results。

其中,$match$group 都是阶段操作符,而阶段 $group 中用到的 $sum 是表达式操作符。

1 阶段操作符

在下面的示例中我们会使用如下集合进行讲解:

>db.article.find().pretty()
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "title" : "MongoDB Aggregate",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ],
    "pages" : 5.0,
    "time" : ISODate("2017-06-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d454"),
    "title" : "MongoDB Index",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Index",
        "Query"
    ],
    "pages" : 3.0,
    "time" : ISODate("2018-11-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "title" : "MongoDB Query",
    "author" : "Aaron",
    "tags" : [
        "Mongodb",
        "Query"
    ],
    "pages" : 8.0,
    "time" : ISODate("2019-06-11T16:00:00.000Z")
}

1.1 $project

$project 用于修改输入文档的结构。可以用来重命名、增加或删除字段(域),也可以用于创建计算结果以及嵌套文档。
示例
返回的文档中只包含_idtages

>db.article.aggregate([{$project:{_id:1,tags:1}}])
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ]
},
{
    "_id" : ObjectId("5c088fec651e67152257d454"),
    "tags" : [
        "Mongodb",
        "Index",
        "Query"
    ]
},
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "tags" : [
        "Mongodb",
        "Query"
    ]
}

新增字段

>db.article.aggregate([{$project:{_id:1,tags:1,editAuthor:'$author'}}])
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ],
    "editAuthor" : "simon"
},
{
    "_id" : ObjectId("5c088fec651e67152257d454"),
    "tags" : [
        "Mongodb",
        "Index",
        "Query"
    ],
    "editAuthor" : "simon"
},
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "tags" : [
        "Mongodb",
        "Query"
    ],
    "editAuthor" : "Aaron"
}

1.2 $match

$match用于过滤数据,只输出符合条件的文档。
示例
查询出文档中 author 为 simon的数据

>db.article.aggregate([{$match:{author:'simon'}}])
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "title" : "MongoDB Aggregate",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ],
    "pages" : 5.0,
    "time" : ISODate("2017-06-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d454"),
    "title" : "MongoDB Index",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Index",
        "Query"
    ],
    "pages" : 3.0,
    "time" : ISODate("2018-11-11T16:00:00.000Z")
}

1.3 $group

$group用于将集合中的文档分组,可用于统计结果
示例
统计每个作者写的文章篇数

>db.article.aggregate([{$group:{_id:'$author',total:{$sum:1}}}])
{
    "_id" : "Aaron",
    "total" : 1.0
},
{
    "_id" : "simon",
    "total" : 2.0
}

1.4 $sort

对集合中的文档进行排序
示例
让集合按照页数进行升序排序

>db.article.aggregate([{$sort:{pages:1}}])
{
    "_id" : ObjectId("5c088fec651e67152257d454"),
    "title" : "MongoDB Index",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Index",
        "Query"
    ],
    "pages" : 3.0,
    "time" : ISODate("2018-11-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "title" : "MongoDB Aggregate",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ],
    "pages" : 5.0,
    "time" : ISODate("2017-06-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "title" : "MongoDB Query",
    "author" : "Aaron",
    "tags" : [
        "Mongodb",
        "Query"
    ],
    "pages" : 8.0,
    "time" : ISODate("2019-06-11T16:00:00.000Z")
}

注意
如果以降序排列,则设置成 pages: -1

1.5 $unwind

将文档中数组类型的字段拆分成多条,每条文档包含数组中的一个值
示例
将集合中 tags字段进行拆分

>db.article.aggregate([{$match:{author:'Aaron'}},{$unwind:'$tags'}])
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "title" : "MongoDB Query",
    "author" : "Aaron",
    "tags" : "Mongodb",
    "pages" : 8.0,
    "time" : ISODate("2019-06-11T16:00:00.000Z")
},
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "title" : "MongoDB Query",
    "author" : "Aaron",
    "tags" : "Query",
    "pages" : 8.0,
    "time" : ISODate("2019-06-11T16:00:00.000Z")
}

注意

  • $unwind参数不是一个数组类型时,将会抛出异常
  • $unwind所作的修改,只用于输出,不能改变原文档

1.6 $limit

限制返回文档的数量
示例
返回集合的前一条文档

>db.article.aggregate([{$limit: 1}])
{
    "_id" : ObjectId("5c088fec651e67152257d453"),
    "title" : "MongoDB Aggregate",
    "author" : "simon",
    "tags" : [
        "Mongodb",
        "Database",
        "Query"
    ],
    "pages" : 5.0,
    "time" : ISODate("2017-06-11T16:00:00.000Z")
}

1.7 $skip

跳过指定数量的文档,并返回余下的文档
示例
跳过集合的前两个文档

>db.article.aggregate([{$skip: 2}])
{
    "_id" : ObjectId("5c088fec651e67152257d455"),
    "title" : "MongoDB Query",
    "author" : "Aaron",
    "tags" : [
        "Mongodb",
        "Query"
    ],
    "pages" : 8.0,
    "time" : ISODate("2019-06-11T16:00:00.000Z")
}

2表达式操作符

表达式操作符有很多操作类型,其中最常用的有布尔聚合操作、集合操作、比较聚合操作、算术聚合操作、字符串聚合操作、数组聚合操作、日期聚合操作、条件聚合操作、数据类型聚合操作等

2.1 布尔聚合操作

  • $and
  • $or
  • $not

示例

>db.getCollection('col').find()
{
    "_id" : ObjectId("5c08c5b5651e67152257d45b"),
    "name" : "a",
    "classes" : "classe 1",
    "score" : 90.0
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45c"),
    "name" : "b",
    "classes" : "classe 2",
    "score" : 50.0
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45d"),
    "name" : "c",
    "classes" : "classe 3",
    "score" : 60.0
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45e"),
    "name" : "d",
    "classes" : "classe 4",
    "score" : 70.0
}

判断成绩是否大于80或者小于50

>db.col.aggregate(
   [
     {
       $project:
          {
            name: 1,
            score:1,  
            result: { $or: [ { $gt: [ "$score", 80 ] }, { $lt: [ "$score", 50 ] } ] }
          }
     }
   ]
)
{
    "_id" : ObjectId("5c08c5b5651e67152257d45b"),
    "name" : "a",
    "score" : 90.0,
    "result" : true
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45c"),
    "name" : "b",
    "score" : 50.0,
    "result" : false
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45d"),
    "name" : "c",
    "score" : 60.0,
    "result" : false
},
{
    "_id" : ObjectId("5c08c5b5651e67152257d45e"),
    "name" : "d",
    "score" : 70.0,
    "result" : false
}

2.2 集合操作

  • $setEquals 除了重复元素外,包括的元素相同
  • $setIntersection 交集
  • $setUnion 并集
  • $setDifference 只在前一集合出现,也就是后一个集合的补集
  • $setIsSubset 前一个集合是后一个集合的子集
  • $anyElementTrue 一个集合内,只要一个元素为真,则返回true
  • $allElementsTrue 一个集合内,所有的元素都为真,则返回true

示例

>db.col.find()
{
    "_id" : ObjectId("5c08c98d651e67152257d45f"),
    "A" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ]
},
{
    "_id" : ObjectId("5c08c98d651e67152257d460"),
    "A" : [ 
        "java", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ]
},
{
    "_id" : ObjectId("5c08c98d651e67152257d461"),
    "A" : [ 
        "java", 
        "c++"
    ],
    "B" : []
}

计算A和B集合的

>db.col.aggregate(
   [
     { $project: { A:1, B: 1, union: { $setIntersection: [ "$A", "$B" ] }} }
   ]
)
{
    "_id" : ObjectId("5c08c98d651e67152257d45f"),
    "A" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "union" : [ 
        "c++", 
        "java", 
        "phython"
    ]
},
{
    "_id" : ObjectId("5c08c98d651e67152257d460"),
    "A" : [ 
        "java", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "union" : [ 
        "c++", 
        "java"
    ]
},
{
    "_id" : ObjectId("5c08c98d651e67152257d461"),
    "A" : [ 
        "java", 
        "c++"
    ],
    "B" : [],
    "union" : []
}

2.3 比较操作

  • $cmd 两个值相等返回0,前值大于后值返回1,前值小于后值返回-1
  • $eq 是否相等
  • $gt 前值是否大于后值
  • $gte 前值是否大于等于后值
  • $lt 前值是否小于后值
  • $lte 前值是否小于等于后值
  • $ne 是否不相等

示例

>db.col.find()
{
    "_id" : ObjectId("5c08cbb3651e67152257d463"),
    "score" : 80.0
}

score 大于等于 80

>db.col.aggregate(
[
    {$project:{_id:1,score:1,result:{$gte:['$score',80]}}}
]
)
{
    "_id" : ObjectId("5c08cbb3651e67152257d463"),
    "score" : 80.0,
    "result" : true
}

2.4 算数聚合操作

  • $abs 绝对值
  • $add
  • $ceil 向上取整
  • $divide
  • $exp 几次方
  • $floor 向下取整
  • $ln 自然对数
  • $log 对数
  • $log10 以10为底的对数
  • $mod 取模
  • $multiply
  • $pow 指数
  • $sqrt 平方根
  • $subtract
  • $trunc 截掉小数取整

示例
score 加 10

db.col.aggregate(
[
    {$project:{_id:1,score:1,result:{$add:['$score',10]}}}
]
)
{
    "_id" : ObjectId("5c08cbb3651e67152257d463"),
    "score" : 80.0,
    "result" : 90.0
}

2.5 字符串聚合操作

  • $concat 字符串连接
  • $indexOfBytes 子串位置(字节)
  • $indexOfCP 子串位置(字符)
  • $split 分割字符串
  • $strLenBytes 字节长度
  • $strLenCP 字符长度
  • $strcasecmp 字符串比较
  • $substrBytes 创建子串(按字节)
  • $substrCP 创建子串(按字符)
  • $toLower 小写
  • $toUpper 大写

示例

>db.col.find()
{
    "_id" : ObjectId("5c08cf2d651e67152257d464"),
    "name" : "abcdefgAAADccsD"
}

将 name 值大写

>db.col.aggregate([
   {
     $project: {name: 1,result:{$toUpper:'$name'}}
   }
])
{
    "_id" : ObjectId("5c08cf2d651e67152257d464"),
    "name" : "abcdefgAAADccsD",
    "result" : "ABCDEFGAAADCCSD"
}

2.6 数组聚合操作

  • $arrayElemAt 返回指定数组索引中的元素
  • $concatArrays 数组连接
  • $filter 返回筛选后的数组
  • $indexOfArray 索引
  • $isArray 是否是数组
  • $range 创建数值数组
  • $reverseArray 反转数组
  • $reduce 对数组中的每个元素应用表达式,并将它们组合成一个值
  • $size 数组元素个数
  • $slice 子数组
  • $zip 合并数组
  • $in 返回一个布尔值,表示指定的值是否在数组中

示例

>db.col.find()
{
    "A" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ]
}

判断指定元素是否在数组中

db.col.aggregate([
   {
     $project: {A: 1,B:1,result:{$in:['java','$A']}}
   }
])
{
    "_id" : ObjectId("5c08c98d651e67152257d45f"),
    "A" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "B" : [ 
        "java", 
        "phython", 
        "c++"
    ],
    "result" : true
}

2.7 日期聚合操作

  • $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.col.find()
{
    "_id" : ObjectId("5c08d61d651e67152257d465"),
    "date" : ISODate("2018-12-06T07:56:13.930Z")
}

日期聚合操作

>db.col.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" }
         }
     }
   ]
)
{
    "_id" : ObjectId("5c08d61d651e67152257d465"),
    "year" : 2018,
    "month" : 12,
    "day" : 6,
    "hour" : 7,
    "minutes" : 56,
    "seconds" : 13,
    "milliseconds" : 930,
    "dayOfYear" : 340,
    "dayOfWeek" : 5,
    "week" : 48
}

2.8 数据类型集合操作

  • $type 返回字段类型

示例

>db.col.aggregate(
   [
     {
       $project:
         {
           date:1,  
           type:{$type:'$date'}
         }
     }
   ]
)
{
    "_id" : ObjectId("5c08d61d651e67152257d465"),
    "date" : ISODate("2018-12-06T07:56:13.930Z"),
    "type" : "date"
}

3 聚合管道的优化与限制

3.1 优化

默认情况下,在整个集合作为聚合管道的输入情况下,为了提高处理数据的效率,可以使用一下策略:

  • $match$sort 放到管道的前面,可以给集合建立索引,来提高处理数据的效率
  • 可以用 $match$limit$skip 对文档进行提前过滤,以减少后续处理文档的数量

当聚合管道执行命令时,MongoDB 也会对各个阶段自动进行优化,主要包括以下几个情况:

  • $sort + $match 顺序优化:如果$match 出现在 $sort 之后,优化器会自动把 $match 放到 $sort 前面
  • $skip + $limit 顺序优化:如果 $skip$limit 之后,优化器会把 $limit 移动到 $skip 的前面,移动后 $limit的值等于原来的值加上 $skip 的值。例如:移动前:{$skip: 10, $limit: 5},移动后:{$limit: 15, $skip: 10}

3.2 限制

  • 返回结果大小:聚合结果返回的是一个文档,不能超过 16M,从 MongoDB 2.6版本以后,返回的结果可以是一个游标或者存储到集合中,返回的结果不受 16M 的限制
  • 内存:聚合管道的每个阶段最多只能用 100M 的内存,如果超过100M,会报错,如果需要处理大数据,可以使用 allowDiskUse 选项,存储到磁盘上

链接:https://www.jianshu.com/p/f1d4300c0067
来源:简书

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351