MongoDB的聚合操作主要是對數(shù)據(jù)的批量處理。一般都是將記錄按條件分組之后進(jìn)行一系列求最大值,最小值,平均值的簡單操作,也可以對記錄進(jìn)行數(shù)據(jù)統(tǒng)計,數(shù)據(jù)挖掘的復(fù)雜操作。聚合操作的輸入是集中的文檔,輸出可以是一個文檔也可以是多個文檔。
MongoDB 提供了三種強(qiáng)大的聚合操作:
Pipeline查詢速度快于MapReduce,但是MapReduce的強(qiáng)大之處在于能夠在多臺Server上并行執(zhí)行復(fù)雜的聚合邏輯。MongoDB不允許Pipeline的單個聚合操作占用過多的系統(tǒng)內(nèi)存,如果一個聚合操作消耗20%以上的內(nèi)存,那么MongoDB直接停止操作,并向客戶端輸出錯誤消息。
本篇主要講解 單目的聚合操作(Single Purpose Aggregation Operation)和聚合管道(Aggregation Pipeline)。
一、單目的聚合操作
單目的聚合命令常用的有:count() 和 distinct()。以 distinct() 為例,其工作流程如下:
1.1 count
count用于返回集合中的文檔數(shù)量。
示例
求出集合中 job_base-accdate 值大于 2018-11-06 的文檔個數(shù)
db.getCollection('job_create').find({'job_base-accdate': {$gt: new Date('06/11/2018')}}).count()
等價于
db.getCollection('job_create').count({'job_base-accdate': {$gt: new Date('06/11/2018')}})
1.2 distinct
distinct用于去重
示例
對 job_base-jid 值進(jìn)行去重操作
db.getCollection('job_create').distinct('job_base-jid')
二、聚合管道
MongoDB 中使用 db.COLLECTION_NAME.aggregate([{<stage>},...]) 方法來構(gòu)建和使用聚合管道,每個文檔通過一個由多個階段(stage)組成的管道,可以對每個階段的管道進(jìn)行分組、過濾等功能,然后經(jīng)過一系列的處理,輸出相應(yīng)的結(jié)果。聚合管道的工作流程如下:
-
$match用于獲取status = "A"的記錄,然后將符合條件的記錄送到下一階段 -
$group中進(jìn)行分組求和計算,最后返回 Results。
其中,$match、$group 都是階段操作符,而階段 $group 中用到的 $sum 是表達(dá)式操作符。
2.1 階段操作符
在下面的示例中我們會使用如下集合進(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)建計算結(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)計結(jié)果
示例
統(tǒng)計每個作者寫的文章篇數(shù)
>db.article.aggregate([{$group:{_id:'$author',total:{$sum:1}}}])
{
"_id" : "Aaron",
"total" : 1.0
},
{
"_id" : "simon",
"total" : 2.0
}
2.1.4 $sort
對集合中的文檔進(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ù)組中的一個值
示例
將集合中 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ù)不是一個數(shù)組類型時,將會拋出異常 -
$unwind所作的修改,只用于輸出,不能改變原文檔
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ù)量的文檔,并返回余下的文檔
示例
跳過集合的前兩個文檔
>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),也就是后一個集合的補(bǔ)集 -
$setIsSubset前一個集合是后一個集合的子集 -
$anyElementTrue一個集合內(nèi),只要一個元素為真,則返回true -
$allElementsTrue一個集合內(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" : []
}
計算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兩個值相等返回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絕對值 -
$add和 -
$ceil向上取整 -
$divide除 -
$exp幾次方 -
$floor向下取整 -
$ln自然對數(shù) -
$log對數(shù) -
$log10以10為底的對數(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對數(shù)組中的每個元素應(yīng)用表達(dá)式,并將它們組合成一個值 -
$size數(shù)組元素個數(shù) -
$slice子數(shù)組 -
$zip合并數(shù)組 -
$in返回一個布爾值,表示指定的值是否在數(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時(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.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)情況下,在整個集合作為聚合管道的輸入情況下,為了提高處理數(shù)據(jù)的效率,可以使用一下策略:
- 將
$match和$sort放到管道的前面,可以給集合建立索引,來提高處理數(shù)據(jù)的效率 - 可以用
$match、$limit、$skip對文檔進(jìn)行提前過濾,以減少后續(xù)處理文檔的數(shù)量
當(dāng)聚合管道執(zhí)行命令時,MongoDB 也會對各個階段自動進(jìn)行優(yōu)化,主要包括以下幾個情況:
-
$sort+$match順序優(yōu)化:如果$match出現(xiàn)在$sort之后,優(yōu)化器會自動把$match放到$sort前面 -
$skip+$limit順序優(yōu)化:如果$skip在$limit之后,優(yōu)化器會把$limit移動到$skip的前面,移動后$limit的值等于原來的值加上$skip的值。例如:移動前:{$skip: 10, $limit: 5},移動后:{$limit: 15, $skip: 10}
2.3.2 限制
- 返回結(jié)果大?。壕酆辖Y(jié)果返回的是一個文檔,不能超過 16M,從 MongoDB 2.6版本以后,返回的結(jié)果可以是一個游標(biāo)或者存儲到集合中,返回的結(jié)果不受 16M 的限制
- 內(nèi)存:聚合管道的每個階段最多只能用 100M 的內(nèi)存,如果超過100M,會報錯,如果需要處理大數(shù)據(jù),可以使用 allowDiskUse 選項,存儲到磁盤上