MongoDB中聚合(aggregate)主要用於處理數據(諸如統計平均值,求和等),並返回計算后的數據結果。有點類似sql語句中的 count(*)。
aggregate() 方法
MongoDB中聚合的方法使用aggregate()。
語法
aggregate() 方法的基本語法格式如下所示:
>db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)
下表展示了一些聚合的表達式:
表達式 | 描述 | 實例 |
---|---|---|
$sum | 計算總和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) |
$avg | 計算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) |
$min | 獲取集合中所有文檔對應值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) |
$max | 獲取集合中所有文檔對應值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) |
$push | 在結果文檔中插入值到一個數組中。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) |
$addToSet | 在結果文檔中插入值到一個數組中,但不創建副本。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) |
$first | 根據資源文檔的排序獲取第一個文檔數據。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) |
$last | 根據資源文檔的排序獲取最后一個文檔數據 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) |
管道的概念
管道在Unix和Linux中一般用於將當前命令的輸出結果作為下一個命令的參數。
MongoDB的聚合管道將MongoDB文檔在一個管道處理完畢后將結果傳遞給下一個管道處理。管道操作是可以重復的。
表達式:處理輸入文檔並輸出。表達式是無狀態的,只能用於計算當前聚合管道的文檔,不能處理其它的文檔。
這里我們介紹一下聚合框架中常用的幾個操作:
- $project:修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。
- $match:用於過濾數據,只輸出符合條件的文檔。$match使用MongoDB的標准查詢操作。
- $limit:用來限制MongoDB聚合管道返回的文檔數。
- $skip:在聚合管道中跳過指定數量的文檔,並返回余下的文檔。
- $unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。
- $group:將集合中的文檔分組,可用於統計結果。
- $sort:將輸入文檔排序后輸出。
- $geoNear:輸出接近某一地理位置的有序文檔。
練習:
數據
> db.user.find() { "_id" : ObjectId("5ab8b9495a96a08a5b909000"), "name" : "qlq1", "age" : 20, "se x" : "男" } { "_id" : ObjectId("5ab8b9535a96a08a5b909001"), "name" : "qlq2", "age" : 22, "se x" : "男" } { "_id" : ObjectId("5ab8b9685a96a08a5b909002"), "name" : "qlq3", "age" : 23, "se x" : "女" } { "_id" : ObjectId("5ab8b96e5a96a08a5b909003"), "name" : "qlq4", "age" : 24, "se x" : "女" }
使用方法:使用管道過濾數據之后利用表達式對數據進行操作:
- 測試分組管道與表達式: $group 分組統計
1.按性別分組,並計算男女人數
db.user.aggregate([ {$group:{ _id:"$sex", num:{$sum:1} } }])
解釋:_id:"$sex"表示按sex屬性分組; $sum表示求和,如果是$sum:1就相當於count(*),一行記錄算一個
2.按性別分組,計算年齡和:
db.user.aggregate([ {$group:{ _id:"$sex", num:{$sum:"$age"} } }])
3.按性別分組,並拿到每個組的第一個年齡:
db.user.aggregate([ {$group:{ _id:"$sex", num:{$first:"$age"} } }])
4.先按性別分組,分完組之后將age屬性映射到數組中:(相當於分完組之后查看同組的數據,mysql不能實現)
db.user.aggregate([ {$group:{ _id:"$sex", num:{$push:"$age"} } }])
如果是將所有列都添加到數組中用 $push:$$ROOT
db.user.aggregate([ {$group:{ _id:"$sex", num:{$push:"$$ROOT"} } }])
結果:
/* 1 */ { "_id" : "女", "num" : [ { "_id" : ObjectId("5ab8b9685a96a08a5b909002"), "name" : "qlq3", "age" : 23.0, "sex" : "女" }, { "_id" : ObjectId("5ab8b96e5a96a08a5b909003"), "name" : "qlq4", "age" : 24.0, "sex" : "女" } ] } /* 2 */ { "_id" : "男", "num" : [ { "_id" : ObjectId("5ab8b9495a96a08a5b909000"), "name" : "qlq1", "age" : 20.0, "sex" : "男" }, { "_id" : ObjectId("5ab8b9535a96a08a5b909001"), "name" : "qlq2", "age" : 22.0, "sex" : "男" } ] }
- $match管道: 類似於find,只是find不能統計,現在是可以過濾並統計
1.查詢年齡大學23小於等於50的(只是過濾)
db.user.aggregate([ { $match:{ age:{$gt:23,$lte:50} } } ])
2.在上面過濾的基礎上聚合(先過濾,再分組)
db.user.aggregate([ { $match:{ age:{$gt:23,$lte:50}, } }, { $group:{ _id:"$sex", num:{ $sum:1 } } } ])
- $project 修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。類似於find方法的第二個參數
1.查詢年齡大學23小於等於50的,按性別分組並統計人數,並且只取人數列:
db.user.aggregate([ { $match:{ age:{$gt:23,$lte:50}, } }, { $group:{ _id:"$sex", num:{ $sum:1 } } }, { $project:{ _id:0, num:1 } } ])
解釋: $project:{_id:0,num:1}表示在結果中取num列,不取_id列。
- $sort 排序。類似於sort方法,指定一列並指明排序方式
1.查詢年齡大於21小於等於35並且按性別分組之后兩列都取,按總數降序排列
db.user.aggregate([ { $match:{ age:{$gt:21,$lte:50}, } }, { $group:{ _id:"$sex", num:{ $sum:1 } } }, { $project:{ _id:1, num:1 } }, { $sort:{num:-1} } ])
- $skip 跳過幾列: $limit:取幾列
例如:在上面排序例子的基礎上先跳過1列,取1個值
db.user.aggregate([ { $match:{ age:{$gt:21,$lte:50}, } }, { $group:{ _id:"$sex", num:{ $sum:1 } } }, { $project:{ _id:1, num:1 } }, { $sort:{num:-1} }, { $skip:1 }, { $limit:1 } ])
例如:在上面排序例子的基礎上先取1列,再跳過1列 (取不到數據)
db.user.aggregate([ { $match:{ age:{$gt:21,$lte:50}, } }, { $group:{ _id:"$sex", num:{ $sum:1 } } }, { $project:{ _id:1, num:1 } }, { $sort:{num:-1} }, { $limit:1 }, { $skip:1 } ])
- $unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。 將數組拆分成一個一個的數據 (相當於分組的逆操作)
構造數據:
> db.tshirt.insert({name:'t1',size:90,size:["x","xxx","M"]}) WriteResult({ "nInserted" : 1 }) > db.tshirt.find() { "_id" : ObjectId("5ab8c3ce807bacd3133efcf8"), "name" : "t1", "size" : [ "x", " xxx", "M" ] } >
例如;按size拆分數據
db.tshirt.aggregate([ { $unwind:'$size' } ])
對於特殊情況的處理:(非數組,不存在的列,為空數組,為null)
構造數據:
> db.tshirt.find() { "_id" : ObjectId("5ab8c5ea807bacd3133efcfd"), "name" : "t1", "price" : 90, "size" : [ "m", "xx", "l" ] } { "_id" : ObjectId("5ab8c5f2807bacd3133efcfe"), "name" : "t1", "price" : 90, "size" : [ ] } { "_id" : ObjectId("5ab8c608807bacd3133efcff"), "name" : "t3", "price" : 93, "size" : "3x" } { "_id" : ObjectId("5ab8c62e807bacd3133efd02"), "name" : "t3", "price" : 93 } { "_id" : ObjectId("5ab8c746807bacd3133efd03"), "name" : "t3", "price" : 93, "size" : null } >
1.直接拆分:()
db.tshirt.aggregate([ { $unwind:'$size' } ])
結果:發現數據丟失(字段不存在的和屬性值為null的數據丟失)
2.拆分且防止數據丟失
db.tshirt.aggregate([ { $unwind:{ path:"$size", preserveNullAndEmptyArrays:true #為true表示防止空數組和null丟失 } } ])
例如:按價格進行分組之后將數據映射到數組中,並按此列拆分數據
db.tshirt.aggregate([ { $group:{ _id:"$price", docs:{ $push:"$$ROOT" } } }, { $project:{ _id:1, docs:1 } }, { $unwind:{ path:"$docs", preserveNullAndEmptyArrays:true } } ])
結果:
/* 1 */ { "_id" : 93.0, "docs" : { "_id" : ObjectId("5ab8c608807bacd3133efcff"), "name" : "t3", "price" : 93.0, "size" : "3x" } } /* 2 */ { "_id" : 93.0, "docs" : { "_id" : ObjectId("5ab8c62e807bacd3133efd02"), "name" : "t3", "price" : 93.0 } } /* 3 */ { "_id" : 93.0, "docs" : { "_id" : ObjectId("5ab8c746807bacd3133efd03"), "name" : "t3", "price" : 93.0, "size" : null } } /* 4 */ { "_id" : 90.0, "docs" : { "_id" : ObjectId("5ab8c5ea807bacd3133efcfd"), "name" : "t1", "price" : 90.0, "size" : [ "m", "xx", "l" ] } } /* 5 */ { "_id" : 90.0, "docs" : { "_id" : ObjectId("5ab8c5f2807bacd3133efcfe"), "name" : "t1", "price" : 90.0, "size" : [] } }