MongoDB數據庫聚合


前面的話

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM