MongoDB的聚合操作主要是對(duì)數(shù)據(jù)的批量處理晤愧。一般都是將記錄按條件分組之后進(jìn)行一系列求最大值大莫,最小值,平均值的簡單操作官份,也可以對(duì)記錄進(jìn)行數(shù)據(jù)統(tǒng)計(jì)只厘,數(shù)據(jù)挖掘的復(fù)雜操作。聚合操作的輸入是集中的文檔舅巷,輸出可以是一個(gè)文檔也可以是多個(gè)文檔羔味。
MongoDB 提供了三種強(qiáng)大的聚合操作:
Pipeline查詢速度快于MapReduce,但是MapReduce的強(qiáng)大之處在于能夠在多臺(tái)Server上并行執(zhí)行復(fù)雜的聚合邏輯钠右。MongoDB不允許Pipeline的單個(gè)聚合操作占用過多的系統(tǒng)內(nèi)存赋元,如果一個(gè)聚合操作消耗20%以上的內(nèi)存,那么MongoDB直接停止操作飒房,并向客戶端輸出錯(cuò)誤消息搁凸。
本篇主要講解 單目的聚合操作(Single Purpose Aggregation Operation)和聚合管道(Aggregation Pipeline)。
一狠毯、單目的聚合操作
單目的聚合命令常用的有:count() 和 distinct()护糖。以 distinct() 為例,其工作流程如下:
1.1 count
count用于返回集合中的文檔數(shù)量垃你。
示例
求出集合中 job_base-accdate 值大于 2018-11-06 的文檔個(gè)數(shù)
db.getCollection('job_create').find({'job_base-accdate': {$gt: new Date('06/11/2018')}}).count()
等價(jià)于
db.getCollection('job_create').count({'job_base-accdate': {$gt: new Date('06/11/2018')}})
1.2 distinct
distinct用于去重
示例
對(duì) job_base-jid 值進(jìn)行去重操作
db.getCollection('job_create').distinct('job_base-jid')
二椅文、聚合管道
MongoDB 中使用 db.COLLECTION_NAME.aggregate([{<stage>},...])
方法來構(gòu)建和使用聚合管道,每個(gè)文檔通過一個(gè)由多個(gè)階段(stage)組成的管道惜颇,可以對(duì)每個(gè)階段的管道進(jìn)行分組、過濾等功能少辣,然后經(jīng)過一系列的處理凌摄,輸出相應(yīng)的結(jié)果。聚合管道的工作流程如下:
-
$match
用于獲取status = "A"
的記錄漓帅,然后將符合條件的記錄送到下一階段 -
$group
中進(jìn)行分組求和計(jì)算锨亏,最后返回 Results。
其中忙干,$match
器予、$group
都是階段操作符,而階段 $group
中用到的 $sum
是表達(dá)式操作符捐迫。
2.1 階段操作符
在下面的示例中我們會(huì)使用如下集合進(jìn)行講解:
>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")
}
2.1.1 $project
$project
用于修改輸入文檔的結(jié)構(gòu)乾翔。可以用來重命名、增加或刪除字段(域)反浓,也可以用于創(chuàng)建計(jì)算結(jié)果以及嵌套文檔萌丈。
示例
返回的文檔中只包含_id
和tages
>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"
}
2.1.2 $match
$match
用于過濾數(shù)據(jù),只輸出符合條件的文檔雷则。
示例
查詢出文檔中 author 為 simon的數(shù)據(jù)
>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")
}
2.1.3 $group
$group
用于將集合中的文檔分組辆雾,可用于統(tǒng)計(jì)結(jié)果
示例
統(tǒng)計(jì)每個(gè)作者寫的文章篇數(shù)
>db.article.aggregate([{$group:{_id:'$author',total:{$sum:1}}}])
{
"_id" : "Aaron",
"total" : 1.0
},
{
"_id" : "simon",
"total" : 2.0
}
2.1.4 $sort
對(duì)集合中的文檔進(jìn)行排序
示例
讓集合按照頁數(shù)進(jìn)行升序排序
>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")
}
注意
如果以降序排列,則設(shè)置成 pages: -1
2.1.5 $unwind
將文檔中數(shù)組類型的字段拆分成多條月劈,每條文檔包含數(shù)組中的一個(gè)值
示例
將集合中 tags
字段進(jìn)行拆分
>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
參數(shù)不是一個(gè)數(shù)組類型時(shí)度迂,將會(huì)拋出異常 -
$unwind
所作的修改,只用于輸出猜揪,不能改變?cè)臋n
2.1.6 $limit
限制返回文檔的數(shù)量
示例
返回集合的前一條文檔
>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")
}
2.1.7 $skip
跳過指定數(shù)量的文檔英岭,并返回余下的文檔
示例
跳過集合的前兩個(gè)文檔
>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表達(dá)式操作符
表達(dá)式操作符有很多操作類型,其中最常用的有布爾聚合操作湿右、集合操作诅妹、比較聚合操作、算術(shù)聚合操作毅人、字符串聚合操作吭狡、數(shù)組聚合操作、日期聚合操作丈莺、條件聚合操作划煮、數(shù)據(jù)類型聚合操作等
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.2 集合操作
-
$setEquals
除了重復(fù)元素外,包括的元素相同 -
$setIntersection
交集 -
$setUnion
并集 -
$setDifference
只在前一集合出現(xiàn)缔俄,也就是后一個(gè)集合的補(bǔ)集 -
$setIsSubset
前一個(gè)集合是后一個(gè)集合的子集 -
$anyElementTrue
一個(gè)集合內(nèi)弛秋,只要一個(gè)元素為真,則返回true -
$allElementsTrue
一個(gè)集合內(nèi)俐载,所有的元素都為真蟹略,則返回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" : []
}
計(jì)算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.2.3 比較操作
-
$cmd
兩個(gè)值相等返回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.2.4 算數(shù)聚合操作
-
$abs
絕對(duì)值 -
$add
和 -
$ceil
向上取整 -
$divide
除 -
$exp
幾次方 -
$floor
向下取整 -
$ln
自然對(duì)數(shù) -
$log
對(duì)數(shù) -
$log10
以10為底的對(duì)數(shù) -
$mod
取模 -
$multiply
乘 -
$pow
指數(shù) -
$sqrt
平方根 -
$subtract
減 -
$trunc
截掉小數(shù)取整
示例
score 加 10
db.col.aggregate(
[
{$project:{_id:1,score:1,result:{$add:['$score',10]}}}
]
)
{
"_id" : ObjectId("5c08cbb3651e67152257d463"),
"score" : 80.0,
"result" : 90.0
}
2.2.5 字符串聚合操作
-
$concat
字符串連接 -
$indexOfBytes
子串位置(字節(jié)) -
$indexOfCP
子串位置(字符) -
$split
分割字符串 -
$strLenBytes
字節(jié)長度 -
$strLenCP
字符長度 -
$strcasecmp
字符串比較 -
$substrBytes
創(chuàng)建子串(按字節(jié)) -
$substrCP
創(chuàng)建子串(按字符) -
$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.2.6 數(shù)組聚合操作
-
$arrayElemAt
返回指定數(shù)組索引中的元素 -
$concatArrays
數(shù)組連接 -
$filter
返回篩選后的數(shù)組 -
$indexOfArray
索引 -
$isArray
是否是數(shù)組 -
$range
創(chuàng)建數(shù)值數(shù)組 -
$reverseArray
反轉(zhuǎn)數(shù)組 -
$reduce
對(duì)數(shù)組中的每個(gè)元素應(yīng)用表達(dá)式挖炬,并將它們組合成一個(gè)值 -
$size
數(shù)組元素個(gè)數(shù) -
$slice
子數(shù)組 -
$zip
合并數(shù)組 -
$in
返回一個(gè)布爾值,表示指定的值是否在數(shù)組中
示例
>db.col.find()
{
"A" : [
"java",
"phython",
"c++"
],
"B" : [
"java",
"phython",
"c++"
]
}
判斷指定元素是否在數(shù)組中
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.2.7 日期聚合操作
-
$dayOfYear
日(1-366) -
$dayOfMonth
月(1-23) -
$dayOfWeek
星期(1 (Sunday) 到 7 (Saturday)) -
$year
年 -
$month
月(1-12) -
$week
周(0-53) -
$hour
時(shí)(0-23) -
$minute
分(0-59) -
$second
秒(0-60) -
$millisecond
毫秒(0-999) -
$dateToString
返回格式化字符串的日期 -
$isoDayOfWeek
以ISO 8601格式返回星期幾 -
$isoWeek
以ISO 8601格式返回周號(hào)状婶,范圍從1到53 -
$isoWeekYear
以ISO 8601格式返回年份編號(hào)
示例
>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.2.8 數(shù)據(jù)類型集合操作
-
$type
返回字段類型
示例
>db.col.aggregate(
[
{
$project:
{
date:1,
type:{$type:'$date'}
}
}
]
)
{
"_id" : ObjectId("5c08d61d651e67152257d465"),
"date" : ISODate("2018-12-06T07:56:13.930Z"),
"type" : "date"
}
2.3 聚合管道的優(yōu)化與限制
2.3.1 優(yōu)化
默認(rèn)情況下意敛,在整個(gè)集合作為聚合管道的輸入情況下,為了提高處理數(shù)據(jù)的效率膛虫,可以使用一下策略:
- 將
$match
和$sort
放到管道的前面草姻,可以給集合建立索引,來提高處理數(shù)據(jù)的效率 - 可以用
$match
稍刀、$limit
撩独、$skip
對(duì)文檔進(jìn)行提前過濾,以減少后續(xù)處理文檔的數(shù)量
當(dāng)聚合管道執(zhí)行命令時(shí),MongoDB 也會(huì)對(duì)各個(gè)階段自動(dòng)進(jìn)行優(yōu)化跌榔,主要包括以下幾個(gè)情況:
-
$sort
+$match
順序優(yōu)化:如果$match
出現(xiàn)在$sort
之后异雁,優(yōu)化器會(huì)自動(dòng)把$match
放到$sort
前面 -
$skip
+$limit
順序優(yōu)化:如果$skip
在$limit
之后,優(yōu)化器會(huì)把$limit
移動(dòng)到$skip
的前面僧须,移動(dòng)后$limit
的值等于原來的值加上$skip
的值纲刀。例如:移動(dòng)前:{$skip: 10, $limit: 5}
,移動(dòng)后:{$limit: 15, $skip: 10}
2.3.2 限制
- 返回結(jié)果大械F健:聚合結(jié)果返回的是一個(gè)文檔示绊,不能超過 16M,從 MongoDB 2.6版本以后暂论,返回的結(jié)果可以是一個(gè)游標(biāo)或者存儲(chǔ)到集合中面褐,返回的結(jié)果不受 16M 的限制
- 內(nèi)存:聚合管道的每個(gè)階段最多只能用 100M 的內(nèi)存,如果超過100M取胎,會(huì)報(bào)錯(cuò)展哭,如果需要處理大數(shù)據(jù),可以使用 allowDiskUse 選項(xiàng)闻蛀,存儲(chǔ)到磁盤上