前面的話
聚合操作主要用於對數據的批量處理,將記錄按條件分組以后,然后再進行一系列操作,例如,求最大值、最小值、平均值,求和等操作。聚合操作還能夠對記錄進行復雜的操作,主要用於數理統計和數據挖掘。在 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 除 $exp ex $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 選項,存儲到磁盤上