MongoDB聚合總結(jié)

本文是以下兩篇引用的博客的從新整理修改而成

零、引用

MongoDB聚合

MongoDB聚合

一、Aggregate 簡(jiǎn)介

db.collection.aggregate()是基于數(shù)據(jù)處理的聚合管道,每個(gè)文檔通過一個(gè)由多個(gè)階段組成的管道,
可以對(duì)每個(gè)階段的管道進(jìn)行分組、過濾等功能,然后經(jīng)過一系列的處理,輸出相應(yīng)的結(jié)果

二、處理示例圖

mongodb-aggregate.png

三、特點(diǎn):

  1. db.collection.aggregate() 可以多個(gè)管道,能方便地進(jìn)行數(shù)據(jù)的處理
  2. db.collection.aggregate() 使用了MongoDB內(nèi)置的原生操作,聚合效率非常高,支持類似SQL Group By的操作,而不再需要用戶編寫自定義的JavaScript例程
  3. 每個(gè)階段管道限制為100M的內(nèi)存。如果一個(gè)節(jié)點(diǎn)管道超過這個(gè)極限,MongoDB將產(chǎn)生一個(gè)錯(cuò)誤??梢栽O(shè)置allowDiskUse=true 將管道數(shù)據(jù)寫入臨時(shí)文件,解決這個(gè)限制
  4. db.collection.aggregate() 可以作用在分片集合,但結(jié)果不能輸出在分片集合,MapReduce可以作用在分片集合,結(jié)果也可以輸出在分片集合
  5. db.collection.aggregate() 方法可以返回一個(gè)指針(cursor),數(shù)據(jù)放在內(nèi)存中,直接操作。跟Mongo shell 一樣指針操作
  6. db.collection.aggregate() 輸出結(jié)果只能保存在一個(gè)文檔中,BSON Document大小限制為16M??梢酝ㄟ^返回指針解決,版本2.6中后面: db.collection.aggregate()方法返回一個(gè)指針,可以返回任何結(jié)果集的大小

四、aggregate語法:

語法模板: db.collection.aggregate(pipeline,options)

  1. pipeline 類型是Array 語法: db.collection.aggregate([{},...])

  2. $group語法: {$group: {_id:<expression>,<field1>:{<accumulator1>:<expression1>},...}}

    • _id 是要進(jìn)行的分組的key
    • $group可以執(zhí)行的表達(dá)式如下:
      • $sum 計(jì)算總和
      • $avg 計(jì)算平均值
      • $min 根據(jù)分組,獲取集合中所有文檔對(duì)應(yīng)值的最小值
      • $max 根據(jù)分組,獲取集合中所有文檔對(duì)應(yīng)值的最大值
      • $push 將指定的表達(dá)式的值添加到一個(gè)數(shù)組中
      • $addToSet 將表達(dá)式的值添加到一個(gè)集合中(無重復(fù)值)
      • $first 返回每組第一個(gè)文檔,如果排序,按照排序,如果沒有,按照默認(rèn)的存儲(chǔ)的順序的第一個(gè)
      • $last 返回每組最后一個(gè)文檔,如果排序,按照排序,如果沒有,按照默認(rèn)的存儲(chǔ)的順序的最后一個(gè)
  3. $project語法:

    • 可以對(duì)輸入文檔進(jìn)行添加新字段或刪除現(xiàn)有的字段,可以自定哪些字段顯示與不顯示。
  4. $match

    • 根據(jù)條件用于過濾數(shù)據(jù),只輸出符合條件的文檔,如果放在pipeline前面,根據(jù)條件過濾數(shù)據(jù),傳輸?shù)较乱粋€(gè)階段管道,可以提高后續(xù)的數(shù)據(jù)處理效率。還可以放在out之前,對(duì)結(jié)果進(jìn)行再一次過濾。
  5. $redact

    • 字段所處的document結(jié)構(gòu)的級(jí)別
  6. $limit

    • 用來限制MongoDB聚合管道返回的文檔數(shù)
  7. $skip

    • 在聚合管道中跳過指定數(shù)量的文檔,并返回余下的文檔。
  8. $unwind

    • 將文檔中的某一個(gè)數(shù)組類型字段拆分成多條,每條包含數(shù)組中的一個(gè)值。
  9. $sample

    • 隨機(jī)選擇從其輸入指定數(shù)量的文檔。如果是大于或等于5%的collection的文檔,$sample進(jìn)行收集掃描,進(jìn)行排序,然后選擇頂部的文件。因此,$sample 在收集階段是受排序的內(nèi)存限制。
    • 語法: { $sample: { size: <positive integer> } }
  10. $sort

    • 將輸入文檔排序后輸出。
  11. $geoNear

    • 用于地理位置數(shù)據(jù)分析。
  12. $out

    • 必須為pipeline最后一個(gè)階段管道,因?yàn)槭菍⒆詈笥?jì)算結(jié)果寫入到指定的collection中。
  13. $indexStats

    • 返回?cái)?shù)據(jù)集合的每個(gè)索引的使用情況。
    • 語法: { $indexStats: { } }
  14. group

    • 將集合中的文檔分組,可用于統(tǒng)計(jì)結(jié)果,$group首先將數(shù)據(jù)根據(jù)key進(jìn)行分組。
  15. options參數(shù)

    • explain:返回指定aggregate各個(gè)階段管道的執(zhí)行計(jì)劃信息。
    • 他操作返回一個(gè)游標(biāo),包含aggregate執(zhí)行計(jì)劃詳細(xì)信息。

五、代碼示例:

示例代碼過長(zhǎng),可以咯過,建議直接使用Ctrl+F搜索

db.items.insert( [
  {
   "quantity" : 2,
   "price" : 5.0,
   "pnumber" : "p003",
  },{
   "quantity" : 2,
   "price" : 8.0,
   "pnumber" : "p002"
  },{
   "quantity" : 1,
   "price" : 4.0,
   "pnumber" : "p002"
  },{
   "quantity" : 2,
   "price" : 4.0,
   "pnumber" : "p001"
  },{
   "quantity" : 4,
   "price" : 10.0,
   "pnumber" : "p003"
  },{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p001"
  },{
   "quantity" : 10,
   "price" : 20.0,
   "pnumber" : "p003"
  },{
   "quantity" : 5,
   "price" : 10.0,
   "pnumber" : "p002"
  }
])     

// group 示例
> db.items.count()
8
> db.items.aggregate([{$group:{_id:null,count:{$sum:1}}}])
{ "_id" : null, "count" : 8 }

> db.items.aggregate([{$group:{_id:null,total:{$sum:"$quantity"}}}])
{ "_id" : null, "total" : 36 }

> db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}}])
{ "_id" : "p001", "total" : 12 }
{ "_id" : "p002", "total" : 8 }
{ "_id" : "p003", "total" : 16 }

> db.items.aggregate([{$group:{_id:"$pnumber",max:{$max:"$quantity"}}}])
{ "_id" : "p001", "max" : 10 }
{ "_id" : "p002", "max" : 5 }
{ "_id" : "p003", "max" : 10 } 

> db.items.aggregate([{$group:{_id:"$pnumber",min:{$min:"$quantity"}}}])
{ "_id" : "p001", "min" : 2 }
{ "_id" : "p002", "min" : 1 }
{ "_id" : "p003", "min" : 2 }

> db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}}])
{ "_id" : "p001", "total" : 12 }
{ "_id" : "p002", "total" : 8 }
{ "_id" : "p003", "total" : 16 }
> db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}},{$group:{_id:null,max:{$max:"$total"}}}])
{ "_id" : null, "max" : 16 }

 
> db.items.aggregate([{$group:{_id:"$pnumber",price:{$avg:"$price"}}}])
{ "_id" : "p001", "price" : 12 }
{ "_id" : "p002", "price" : 7.333333333333333 }
{ "_id" : "p003", "price" : 11.666666666666666 }

> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$push:"$quantity"}}}])
{ "_id" : "p001", "quantitys" : [ 2, 10 ] }
{ "_id" : "p002", "quantitys" : [ 2, 1, 5 ] }
{ "_id" : "p003", "quantitys" : [ 2, 4, 10 ] }

> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$push:{quantity:"$quantity",price:"$price"}}}}])
{ "_id" : "p001", "quantitys" : [ { "quantity" : 2, "price" : 4 }, { "quantity": 10, "price" : 20 } ] }
{ "_id" : "p002", "quantitys" : [ { "quantity" : 2, "price" : 8 }, { "quantity": 1, "price" : 4 }, { "quantity" : 5, "price" : 10 } ] }
{ "_id" : "p003", "quantitys" : [ { "quantity" : 2, "price" : 5 }, { "quantity": 4, "price" : 10 }, { "quantity" : 10, "price" : 20 } ] }


> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$addToSet:"$quantity"}}}])
{ "_id" : "p001", "quantitys" : [ 10, 2 ] }
{ "_id" : "p002", "quantitys" : [ 5, 1, 2 ] }
{ "_id" : "p003", "quantitys" : [ 10, 4, 2 ] }

> db.items.aggregate([{$group:{_id:"$pnumber",quantityFrist:{$first:"$quantity"}}}])
{ "_id" : "p001", "quantityFrist" : 2 }
{ "_id" : "p002", "quantityFrist" : 2 }
{ "_id" : "p003", "quantityFrist" : 2 }

// $project 示例
> db.items.aggregate([{$group:{_id:null,count:{$sum:1}}},{$project:{"_id":0,"count":1}}])
{ "count" : 8 }


// $match 示例
> db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}},{$match:{total:{$gt:8}}}])
{ "_id" : "p001", "total" : 12 }
{ "_id" : "p003", "total" : 16 }

> db.items.aggregate([{$match:{"pnumber":"p001"}},{$group:{_id:null,total:{$sum:"$quantity"}}}])
{ "_id" : null, "total" : 12 } 

// $skip、$limit、$sort 
//    $limit、$skip、$sort、$match可以使用在階段管道,如果使用在$group之前可以過濾掉一些數(shù)據(jù),提高性能。

> db.items.aggregate([{ $skip: 2 },{ $limit: 4 }])
{ "_id" : ObjectId("574d937cfafef57ee4427ac4"), "quantity" : 1, "price" : 4, "pnumber" : "p002" }
{ "_id" : ObjectId("574d937cfafef57ee4427ac5"), "quantity" : 2, "price" : 4, "pnumber" : "p001" }
{ "_id" : ObjectId("574d937cfafef57ee4427ac6"), "quantity" : 4, "price" : 10, "pnumber" : "p003" }
{ "_id" : ObjectId("574d937cfafef57ee4427ac7"), "quantity" : 10, "price" : 20, "pnumber" : "p001" }
> db.items.aggregate([{ $limit: 4 },{ $skip: 2 }])
{ "_id" : ObjectId("574d937cfafef57ee4427ac4"), "quantity" : 1, "price" : 4, "pnumber" : "p002" }
{ "_id" : ObjectId("574d937cfafef57ee4427ac5"), "quantity" : 2, "price" : 4, "pnumber" : "p001" }

// $unwind
> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$push:"$quantity"}}}])
{ "_id" : "p001", "quantitys" : [ 2, 10 ] }
{ "_id" : "p002", "quantitys" : [ 2, 1, 5 ] }
{ "_id" : "p003", "quantitys" : [ 2, 4, 10 ] }
> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$push:"$quantity"}}},{$unwind:"$quantitys"}])
{ "_id" : "p001", "quantitys" : 2 }
{ "_id" : "p001", "quantitys" : 10 }
{ "_id" : "p002", "quantitys" : 2 }
{ "_id" : "p002", "quantitys" : 1 }
{ "_id" : "p002", "quantitys" : 5 }
{ "_id" : "p003", "quantitys" : 2 }
{ "_id" : "p003", "quantitys" : 4 }
{ "_id" : "p003", "quantitys" : 10 }

// $out
//必須為pipeline最后一個(gè)階段管道,因?yàn)槭菍⒆詈笥?jì)算結(jié)果寫入到指定的collection中。
{ "_id" : "p001", "quantitys" : 2 }
{ "_id" : "p001", "quantitys" : 10 }
{ "_id" : "p002", "quantitys" : 2 }
{ "_id" : "p002", "quantitys" : 1 }
{ "_id" : "p002", "quantitys" : 5 }
{ "_id" : "p003", "quantitys" : 2 }
{ "_id" : "p003", "quantitys" : 4 }
{ "_id" : "p003", "quantitys" : 10 }

> db.items.aggregate([{$group:{_id:"$pnumber",quantitys:{$push:"$quantity"}}},{$unwind:"$quantitys"},{$project:{"_id":0,"quantitys":1}},{$out:"result"}])
> db.result.find()
{ "_id" : ObjectId("57529143746e15e8aa207a29"), "quantitys" : 2 }
{ "_id" : ObjectId("57529143746e15e8aa207a2a"), "quantitys" : 10 }
{ "_id" : ObjectId("57529143746e15e8aa207a2b"), "quantitys" : 2 }
{ "_id" : ObjectId("57529143746e15e8aa207a2c"), "quantitys" : 1 }
{ "_id" : ObjectId("57529143746e15e8aa207a2d"), "quantitys" : 5 }
{ "_id" : ObjectId("57529143746e15e8aa207a2e"), "quantitys" : 2 }
{ "_id" : ObjectId("57529143746e15e8aa207a2f"), "quantitys" : 4 }
{ "_id" : ObjectId("57529143746e15e8aa207a30"), "quantitys" : 10 }

// $redact
/*語法:{ $redact: <expression> }
   $redact 跟$cond結(jié)合使用,并在$cond里面使用了if 、then、else表達(dá)式,if-else缺一不可,$redact還有三個(gè)重要的參數(shù):
     1)$$DESCEND: 返回包含當(dāng)前document級(jí)別的所有字段,并且會(huì)繼續(xù)判字段包含內(nèi)嵌文檔,內(nèi)嵌文檔的字段也會(huì)去判斷是否符合條件。
     2)$$PRUNE:返回不包含當(dāng)前文檔或者內(nèi)嵌文檔級(jí)別的所有字段,不會(huì)繼續(xù)檢測(cè)此級(jí)別的其他字段,即使這些字段的內(nèi)嵌文檔持有相同的訪問級(jí)別。
     3)$$KEEP:返回包含當(dāng)前文檔或內(nèi)嵌文檔級(jí)別的所有字段,不再繼續(xù)檢測(cè)此級(jí)別的其他字段,即使這些字段的內(nèi)嵌文檔中持有不同的訪問級(jí)別。
**/

//  level=1則值為為$$DESCEND,否則為$$PRUNE,從頂部開始掃描下去,執(zhí)行$$DESCEND包含當(dāng)前document級(jí)別的所有fields。當(dāng)前級(jí)別字段的內(nèi)嵌文檔將會(huì)被繼續(xù)檢測(cè)。
db.redact.insert(
  {
  _id: 1,
  level: 1,
  status: "A",
  acct_id: "xyz123",
  cc: [{
    level: 1,
    type: "yy",
    num: 000000000000,
    exp_date: ISODate("2015-11-01T00:00:00.000Z"),
    billing_addr: {
      level: 5,
      addr1: "123 ABC Street",
      city: "Some City"
    }
  },{
     level: 3,
    type: "yy",
    num: 000000000000,
    exp_date: ISODate("2015-11-01T00:00:00.000Z"),
    billing_addr: {
      level: 1,
      addr1: "123 ABC Street",
      city: "Some City"
    }
}]
})
 
db.redact.aggregate(
  [
    { $match: { status: "A" } },
    {
      $redact: {
        $cond: {
          if: { $eq: [ "$level", 1] },
          then: "$$DESCEND",
          else: "$$PRUNE"
        }
      }
    }
  ]
);

{
  "_id" : 1,
  "level" : 1,
  "status" : "A",
  "acct_id" : "xyz123",
  "cc" : [
           { "level" : 1,
     "type" : "yy",
 "num" : 0,
 "exp_date" : ISODate("2015-11-01T00:00:00Z")
}
   ]
 }

// 2. $$PRUNE:不包含當(dāng)前文檔或者內(nèi)嵌文檔級(jí)別的所有字段,不會(huì)繼續(xù)檢測(cè)此級(jí)別的其他字段,即使這些字段的內(nèi)嵌文檔持有相同的訪問級(jí)別。連等級(jí)的字段都不顯示,也不會(huì)去掃描等級(jí)字段包含下級(jí)。
db.redact.insert(
  {
  _id: 1,
  level: 1,
  status: "A",
  acct_id: "xyz123",
  cc: {
    level: 3,
    type: "yy",
    num: 000000000000,
    exp_date: ISODate("2015-11-01T00:00:00.000Z"),
    billing_addr: {
      level: 1,
      addr1: "123 ABC Street",
      city: "Some City"
    }
}
 }
)
db.redact.aggregate(
  [
    { $match: { status: "A" } },
    {
      $redact: {
        $cond: {
          if: { $eq: [ "$level", 3] },
          then: "$$PRUNE",
          else: "$$DESCEND"
        }
      }
    }
  ]
);
{ "_id" : 1, "level" : 1, "status" : "A", "acct_id" : "xyz123" }

//  3、$$KEEP:返回包含當(dāng)前文檔或內(nèi)嵌文檔級(jí)別的所有字段,不再繼續(xù)檢測(cè)此級(jí)別的其他字段,即使這些字段的內(nèi)嵌文檔中持有不同的訪問級(jí)別。
db.redact.insert(
  {
  _id: 1,
  level: 1,
  status: "A",
  acct_id: "xyz123",
  cc: {
    level: 2,
    type: "yy",
    num: 000000000000,
    exp_date: ISODate("2015-11-01T00:00:00.000Z"),
    billing_addr: {
      level:3,
      addr1: "123 ABC Street",
      city: "Some City"
    }
}
 }
)

db.redact.aggregate(
  [
    { $match: { status: "A" } },
    {
      $redact: {
        $cond: {
          if: { $eq: [ "$level", 1] },
          then: "$$KEEP",
          else: "$$PRUNE"
        }
      }
    }
  ]
);
 
{ "_id" : 1, "level" : 1, "status" : "A", "acct_id" : "xyz123", "cc" : { "level" : 2, "type" : "yy", "num" : 0, "exp_date" : ISODate("2015-11-01T00:00:00Z"), "billing_addr" : { "level" : 3, "addr1" : "123 ABC Street", "city" : "Some City" } } }

// opiton 示例
db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}},{$group:{_id:null,max:{$max:"$total"}}}],{explain:true}) 

{
    "stages": [
        {
            "$cursor": {
                "query": {},
                "fields": {
                    "pnumber": 1,
                    "quantity": 1,
                    "_id": 0
                },
                "plan": {
                    "cursor": "BasicCursor",
                    "isMultiKey": false,
                    "scanAndOrder": false,
                    "allPlans": [
                        {
                            "cursor": "BasicCursor",
                            "isMultiKey": false,
                            "scanAndOrder": false
                        }
                    ]
                }
            }
        },
        {
            "$group": {
                "_id": "$pnumber",
                "total": {
                    "$sum": "$quantity"
                }
            }
        },
        {
            "$group": {
                "_id": {
                    "$const": null
                },
                "max": {
                    "$max": "$total"
                }
            }
        }
    ],
    "ok": 1
}

六、其他

  • allowDiskUse:每個(gè)階段管道限制為100MB的內(nèi)存,如果大于100MB的數(shù)據(jù)可以先寫入臨時(shí)文件。設(shè)置為true時(shí),aggregate操作可時(shí)可以先將數(shù)據(jù)寫入對(duì)應(yīng)數(shù)據(jù)目錄的子目錄中的唯一并以_tmp結(jié)尾的文檔中。
  • cursor:指定游標(biāo)的初始批批大小。光標(biāo)的字段的值是一個(gè)與場(chǎng)batchSize文件。
    • 語法: cursor: { batchSize: <int> }
    var cursor=db.items.aggregate([{$group:{_id:"$pnumber",total:{$sum:"$quantity"}}},{ $limit: 2 }],{cursor: { batchSize: 1 }})
    
  • mongodb shell 設(shè)置游標(biāo)大小cursor.batchSize(size) 一次返回多少條,游標(biāo)提供了很多方法:
    • cursor.hasNext()
    • cursor.next()
    • cursor.toArray()
    • cursor.forEach()
    • cursor.map()
    • cursor.objsLeftInBatch()
    • cursor.itcount()
    • cursor.pretty()
  • bypassDocumentValidation:只有當(dāng)你指定了$out操作符,使db.collection.aggregate繞過文檔驗(yàn)證操作過程中。這讓您插入不符合驗(yàn)證要求的文檔
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容