MongoDB 聚合管道

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é)果以及嵌套文檔。
示例
返回的文檔中只包含_idtages

>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 選項,存儲到磁盤上
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • MongoDB 聚合 MongoDB中聚合(aggregate)主要用于處理數(shù)據(jù)(諸如統(tǒng)計平均值,求和等),并返回...
    AllSun閱讀 766評論 0 2
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,602評論 19 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 32,309評論 2 89
  • 我們先介紹一下 MongoDB 的聚合功能,聚合操作主要用于對數(shù)據(jù)的批量處理,往往將記錄按條件分組以后,然后再進(jìn)行...
    一稈子數(shù)碼閱讀 646評論 0 2
  • 某天晚上和一個昆明朋友聊天,發(fā)現(xiàn)他對霧霾沒有任何概念。我作為一個能忍受寒冷和霧霾的北方人,有必要介紹一下我的經(jīng)歷。...
    壹步塵埃閱讀 460評論 11 6

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