信息科學中的聚合是指對相關數據進行內容篩選、處理和歸類並輸出結果的過程。MongoDB 中的聚合是指同時對多個文檔中的數據進行處理、篩選和歸類並輸出結果的過程。數據在聚合操做的過程當中,就像是水流過一節一節的管道同樣,因此 MongoDB 中的聚合又被人稱爲流式聚合。mongodb
MongoDB 提供了幾種聚合方式:數據庫
接下來,咱們將全方位地瞭解 MongoDB 中的聚合。數組
Aggregation Pipeline 又稱聚合管道。開發者能夠將多個文檔傳入一個由多個 Stage
組成的 Pipeline
,每個 Stage
處理的結果將會傳入下一個 Stage
中,最後一個 Stage
的處理結果就是整個 Pipeline
的輸出。bash
建立聚合管道的語法以下:微信
db.collection.aggregate( [ { <stage> }, ... ] )
複製代碼
MongoDB 提供了 23 種 Stage
,它們是:session
Stage | 描述 |
---|---|
$addFields |
向文檔添加新字段。 |
$bucket |
根據指定的表達式和存儲區邊界將傳入的文檔分組。 |
$bucketAuto |
根據指定的表達式將傳入的文檔分類爲特定數量的組,自動肯定存儲區邊界。 |
$collStats |
返回有關集合或視圖的統計信息。 |
$count |
返回聚合管道此階段的文檔數量計數。 |
$facet |
在同一組輸入文檔的單個階段內處理多個聚合操做。 |
$geoNear |
基於與地理空間點的接近度返回有序的文檔流。 |
$graphLookup |
對集合執行遞歸搜索。 |
$group |
按指定的標識符表達式對文檔進行分組。 |
$indexStats |
返回集合的索引信息。 |
$limit |
將未修改的前 n 個文檔傳遞給管道。 |
$listSessions |
列出system.sessions 集合的全部會話。 |
$lookup |
對同一數據庫中的另外一個集合執行左外鏈接。 |
$match |
過濾文檔,僅容許匹配的文檔地傳遞到下一個管道階段。 |
$out |
將聚合管道的結果文檔寫入指定集合,它必須是管道中的最後一個階段。 |
$project |
爲文檔添加新字段或刪除現有字段。 |
$redact |
可用於實現字段級別的編輯。 |
$replaceRoot |
用指定的嵌入文檔替換文檔。該操做將替換輸入文檔中的全部現有字段,包括_id 字段。指定嵌入在輸入文檔中的文檔以將嵌入文檔提高到頂層。 |
$sample |
從輸入中隨機選擇指定數量的文檔。 |
$skip |
跳過前 n 個文檔,並將未修改的其他文檔傳遞到下一個階段。 |
$sort |
按指定的排序鍵從新排序文檔流。只有訂單改變; 文件保持不變。對於每一個輸入文檔,輸出一個文檔。 |
$sortByCount |
對傳入文檔進行分組,而後計算每一個不一樣組中的文檔計數。 |
$unwind |
解構文檔中的數組字段。 |
文檔、Stage
和 Pipeline
的關係以下圖所示:函數
$match
、
$sample
和
$project
等三個
Stage
並輸出的過程。SQL 中常見的聚合術語有
WHERE
、
SUM
和
COUNT
等。下表描述了常見的 SQL 聚合術語、函數和概念以及對應的 MongoDB 操做符或
Stage
。
SQL | MongoDB |
---|---|
WHERE | $match |
GROUP BY | $group |
HAVING | $match |
SELECT | $project |
ORDER BY | $sort |
LIMIT | $limit |
SUM() | $sum |
COUNT() | $sum $sortByCount |
join | $lookup |
下面,咱們將經過示例瞭解 Aggregate
、 Stage
和 Pipeline
之間的關係。學習
$match
的描述爲「過濾文檔,僅容許匹配的文檔地傳遞到下一個管道階段」。其語法格式以下:優化
{ $match: { <query> } }
複製代碼
在開始學習以前,咱們須要準備如下數據:ui
> db.artic.insertMany([
... { "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 },
... { "_id" : 2, "author" : "dave", "score" : 85, "views" : 521 },
... { "_id" : 3, "author" : "anna", "score" : 60, "views" : 706 },
... { "_id" : 4, "author" : "line", "score" : 55, "views" : 300 }
... ])
複製代碼
而後咱們創建只有一個 Stage
的 Pipeline
,以實現過濾出 author
爲 dave
的文檔。對應示例以下:
> db.artic.aggregate([
... {$match: {author: "dave"}}
... ])
{ "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 }
{ "_id" : 2, "author" : "dave", "score" : 85, "views" : 521 }
複製代碼
若是要創建有兩個 Stage
的 Pipeline
,那麼就在 aggregate
中添加一個 Stage
便可。如今有這樣一個需求:統計集合 artic
中 score
大於 70
且小於 90
的文檔數量。這個需求分爲兩步進行:
Aggregation 很是適合這種多步驟的操做。在這個場景中,咱們須要用到 $match
、$group
這兩個 Stage
,而後再與聚合表達式 $sum
相結合,對應示例以下:
> db.artic.aggregate([
... {$match: {score: {$gt: 70, $lt: 90}}},
... {$group: {_id: null, number: {$sum: 1}}}
... ])
{ "_id" : null, "number" : 2 }
複製代碼
這個示例的完整過程能夠用下圖表示:
經過上面的描述和舉例,我相信你對Aggregate
、
Stage
和
Pipeline
有了必定的瞭解。接下來,咱們將學習常見的
Stage
的語法和用途。
sample
$sample
的做用是從輸入中隨機選擇指定數量的文檔,其語法格式以下:
{ $sample: { size: <positive integer> } }
複製代碼
假設要從集合 artic
中隨機選擇兩個文檔,對應示例以下:
> db.artic.aggregate([
... {$sample: {size: 2}}
... ])
{ "_id" : 1, "author" : "dave", "score" : 80, "views" : 100 }
{ "_id" : 3, "author" : "anna", "score" : 60, "views" : 706 }
複製代碼
size
對應的值必須是正整數,若是輸入負數會獲得錯誤提示:size argument to $sample must not be negative
。要注意的是,當值超過集合中的文檔數量時,返回結果是集合中的全部文檔,但文檔順序是隨機的。
project
$project
的做用是過濾文檔中的字段,這與投影操做類似,但處理結果將會傳入到下一個階段 。其語法格式以下:
{ $project: { <specification(s)> } }
複製代碼
準備如下數據:
> db.projects.save(
{_id: 1, title: "籃球訓練營青春校園活動開始啦", numb: "A829Sck23", author: {last: "quinn", first: "James"}, hot: 35}
)
複製代碼
假設 Pipeline
中的下一個 Stage
只須要文檔中的 title
和 author
字段,對應示例以下:
> db.projects.aggregate([{$project: {title: 1, author: 1}}])
{ "_id" : 1, "title" : "籃球訓練營青春校園活動開始啦", "author" : { "last" : "quinn", "first" : "James" } }
複製代碼
0
和 1
能夠同時存在。對應示例以下:
> db.projects.aggregate([{$project: {title: 1, author: 1, _id: 0}}])
{ "title" : "籃球訓練營青春校園活動開始啦", "author" : { "last" : "quinn", "first" : "James" } }
複製代碼
true
等效於 1
,false
等效於 0
,也能夠混用布爾值和數字,對應示例以下:
> db.projects.aggregate([{$project: {title: 1, author: true, _id: false}}])
{ "title" : "籃球訓練營青春校園活動開始啦", "author" : { "last" : "quinn", "first" : "James" } }
複製代碼
若是想要排除指定字段,那麼在 $project
中將其設置爲 0
或 false
便可,對應示例以下:
> db.projects.aggregate([{$project: {author: false, _id: false}}])
{ "title" : "籃球訓練營青春校園活動開始啦", "numb" : "A829Sck23", "hot" : 35 }
複製代碼
$project
也能夠做用於嵌入式文檔。對於 author
字段,有時候咱們只須要 FirstName
或者 Lastname
,對應示例以下:
> db.projects.aggregate([{$project: {author: {"last": false}, _id: false, numb: 0}}])
{ "title" : "籃球訓練營青春校園活動開始啦", "author" : { "first" : "James" }, "hot" : 35 }
複製代碼
這裏使用 {author: {"last": false}}
過濾掉 LastName
,但保留 first
。
以上就是 $project
的基本用法和做用介紹,更多與 $project
相關的知識可查閱官方文檔 $project。
lookup
$lookup
的做用是對同一數據庫中的集合執行左外鏈接,其語法格式以下:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
複製代碼
左外鏈接相似與下面的僞 SQL 語句:
SELECT *, <output array field>
FROM collection WHERE <output array field> IN (
SELECT * FROM <collection to join> WHERE
<foreignField>= <collection.localField>);
複製代碼
lookup
支持的指令及對應描述以下:
領域 | 描述 |
---|---|
from |
指定集合名稱。 |
localField |
指定輸入 $lookup 中的字段。 |
foreignField |
指定from 給定的集合中的文檔字段。 |
as |
指定要添加到輸入文檔的新數組字段的名稱。 新數組字段包含 from 集合中的匹配文檔。若是輸入文檔中已存在指定的名稱,則會覆蓋現有字段 。 |
準備如下數據:
> db.sav.insert([
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
{ "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
{ "_id" : 3 }
])
> db.avi.insert([
{ "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
{ "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
{ "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
{ "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
{ "_id" : 5, "sku": null, description: "Incomplete" },
{ "_id" : 6 }
])
複製代碼
假設要鏈接集合 sav
中的 item
和集合 avi
中的 sku
,並將鏈接結果命名爲 savi
。對應示例以下:
> db.sav.aggregate([
{
$lookup:
{
from: "avi",
localField: "item",
foreignField: "sku",
as: "savi"
}
}
])
複製代碼
命令執行後,輸出以下內容:
{
"_id" : 1,
"item" : "almonds",
"price" : 12,
"quantity" : 2,
"savi" : [
{ "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
]
}
{
"_id" : 2,
"item" : "pecans",
"price" : 20,
"quantity" : 1,
"savi" : [
{ "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
]
}
{
"_id" : 3,
"savi" : [
{ "_id" : 5, "sku" : null, "description" : "Incomplete" },
{ "_id" : 6 }
]
}
複製代碼
上面的鏈接操做等效於下面這樣的僞 SQL:
SELECT *, savi
FROM sav
WHERE savi IN (SELECT *
FROM avi
WHERE sku= sav.item);
複製代碼
以上就是 lookup
的基本用法和做用介紹,更多與 lookup
相關的知識可查閱官方文檔 lookup。
unwind
unwind
能將包含數組的文檔拆分稱多個文檔,其語法格式以下:
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
複製代碼
unwind
支持的指令及對應描述以下:
指令 | 類型 | 描述 |
---|---|---|
path |
string | 指定數組字段的字段路徑, 必填。 |
includeArrayIndex |
string | 用於保存元素的數組索引的新字段的名稱。 |
preserveNullAndEmptyArrays |
boolean | 默認狀況下,若是path 爲 null 、缺乏該字段或空數組, 則不輸出文檔。反之,將其設爲 true 則會輸出文檔。 |
在開始學習以前,咱們須要準備如下數據:
> db.shoes.save({_id: 1, brand: "Nick", sizes: [37, 38, 39]})
複製代碼
集合 shoes
中的 sizes
是一個數組,裏面有多個尺碼數據。假設要將這個文檔拆分紅 3 個 size
爲單個值的文檔,對應示例以下:
> db.shoes.aggregate([{$unwind : "$sizes"}])
{ "_id" : 1, "brand" : "Nick", "sizes" : 37 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 38 }
{ "_id" : 1, "brand" : "Nick", "sizes" : 39 }
複製代碼
顯然,這樣的文檔更方便咱們作數據處理。preserveNullAndEmptyArrays
指令默認爲 false
,也就是說文檔中指定的 path
爲空、null
或缺乏該 path
的時候,會忽略掉該文檔。假設數據以下:
> db.shoes2.insertMany([
{"_id": 1, "item": "ABC", "sizes": ["S", "M", "L"]},
{"_id": 2, "item": "EFG", "sizes": [ ]},
{"_id": 3, "item": "IJK", "sizes": "M"},
{"_id": 4, "item": "LMN" },
{"_id": 5, "item": "XYZ", "sizes": null}
])
複製代碼
咱們執行如下命令:
> db.shoes2.aggregate([{$unwind: "$sizes"}])
複製代碼
就會獲得以下輸出:
{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }
複製代碼
_id
爲 2
、4
和 5
的文檔因爲知足 preserveNullAndEmptyArrays
的條件,因此不會被拆分。
以上就是 unwind
的基本用法和做用介紹,更多與 unwind
相關的知識可查閱官方文檔 unwind。
out
out
的做用是聚合 Pipeline
返回的結果文檔,並將其寫入指定的集合。要注意的是,out
操做必須出如今 Pipeline
的最後。out
語法格式以下:
{ $out: "<output-collection>" }
複製代碼
準備如下數據:
> db.books.insertMany([
{ "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 },
{ "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 },
{ "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 },
{ "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 },
{ "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }
])
複製代碼
假設要集合 books
的分組結果保存到名爲 books_result
的集合中,對應示例以下:
> db.books.aggregate([
... { $group : {_id: "$author", books: {$push: "$title"}}},
... { $out : "books_result" }
... ])
複製代碼
命令執行後,MongoDB 將會建立 books_result
集合,並將分組結果保存到該集合中。集合 books_result
中的文檔以下:
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] }
{ "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
複製代碼
以上就是 out
的基本用法和做用介紹,更多與 out
相關的知識可查閱官方文檔 out。
Map-reduce 用於將大量數據壓縮爲有用的聚合結果,其語法格式以下:
db.runCommand(
{
mapReduce: <collection>,
map: <function>,
reduce: <function>,
finalize: <function>,
out: <output>,
query: <document>,
sort: <document>,
limit: <number>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>,
collation: <document>,
writeConcern: <document>
}
)
複製代碼
其中,db.runCommand({mapReduce: <collection>})
也能夠寫成 db.collection.mapReduce()
。各指令的對應描述以下:
指令 | 類型 | 描述 |
---|---|---|
mapReduce |
collection | 集合名稱,必填。 |
map |
function | JavaScript 函數,必填。 |
reduce |
function | JavaScript 函數,必填。 |
out |
string or document | 指定輸出結果,必填。 |
query |
document | 查詢條件語句。 |
sort |
document | 對文檔進行排序。 |
limit |
number | 指定輸入到 map 中的最大文檔數量。 |
finalize |
function | 修改 reduce 的輸出。 |
scope |
document | 指定全局變量。 |
jsMode |
boolean | 是否在執行map 和reduce 函數之間將中間數據轉換爲 BSON 格式,默認 false 。 |
verbose |
boolean | 結果中是否包含 timing 信息,默認 false 。 |
bypassDocumentValidation |
boolean | 是否容許 mapReduce 在操做期間繞過文檔驗證,默認 false 。 |
collation |
document | 指定要用於操做的排序規則。 |
writeConcern |
document | 指定寫入級別,不填寫則使用默認級別。 |
一個簡單的 mapReduce
語法示例以下:
var mapFunction = function() { ... };
var reduceFunction = function(key, values) { ... };
db.runCommand(
... {
... ... mapReduce: <input-collection>,
... ... map: mapFunction,
... ... reduce: reduceFunction,
... ... out: { merge: <output-collection> },
... ... query: <query>
... })
複製代碼
map
函數負責將每一個輸入的文檔轉換爲零個或多個文檔。map
結構以下:
function() {
...
emit(key, value);
}
複製代碼
emit
函數的做用是分組,它接收兩個參數:
key
:指定用於分組的字段。value
:要聚合的字段。在 map
中可使用 this
關鍵字引用當前文檔。reduce
結構以下:
function(key, values) {
...
return result;
}
複製代碼
reduce
執行具體的數據處理操做,它接收兩個參數:
key
:與 map
中的 key
相同,即分組字段。values
:根據分組字段,將相同 key
的值放到同一個數組,values
就是包含這些分類數組的對象。out
用於指定結果輸出,out: <collectionName>
會將結果輸出到新的集合,或者使用如下語法將結果輸出到已存在的集合中:
out: { <action>: <collectionName>
[, db: <dbName>]
[, sharded: <boolean> ]
[, nonAtomic: <boolean> ] }
複製代碼
要注意的是,若是 out
指定的 collection
已存在,那麼它就會覆蓋該集合。在開始學習以前,咱們須要準備如下數據:
> db.mprds.insertMany([
... {_id: 1, numb: 3, score: 9, team: "B"},
... {_id: 2, numb: 6, score: 9, team: "A"},
... {_id: 3, numb: 24, score: 9, team: "A"},
... {_id: 4, numb: 6, score: 8, team: "A"}
... ])
複製代碼
接着定義 map
函數、reduce
函數,並將其應用到集合 mrexample
上。而後爲輸出結果指定存放位置,這裏將輸出結果存放在名爲 mrexample_result
的集合中。
> var func_map = function(){emit(this.numb, this.score);};
> var func_reduce = function(key, values){return Array.sum(values);};
> db.mprds.mapReduce(func_map, func_reduce, {query: {team: "A"}, out: "mprds_result"})
複製代碼
map
函數指定告終果中包含的兩個鍵,並將 this.class
相同的文檔輸出到同一個文檔中。reduce
則對傳入的列表進行求和,求和結果做爲結果中的 value
。命令執行完畢後,結果會被存放在集合 mprds_result
中。用如下命令查看結果:
> db.mprds_result.find()
{ "_id" : 6, "value" : 17 }
{ "_id" : 24, "value" : 9 }
複製代碼
結果文檔中的 _id
即 map
中的 this.numb
,value
爲 reduce
函數的返回值。
下圖描述了這次 mapReduce
操做的完整過程:
finallize
用於修改 reduce
的輸出結果,其語法格式以下:
function(key, reducedValue) {
...
return modifiedObject;
}
複製代碼
它接收兩個參數:
key
,與 map
中的 key
相同,即分組字段。
reducedValue
,一個 Obecjt
,是reduce
的輸出。
上面咱們介紹了 map
和 reduce
,並經過一個簡單的示例瞭解 mapReduce
的基本組成和用法。實際上咱們還能夠編寫功能更豐富的 reduce
函數,甚至使用 finallize
修改 reduce
的輸出結果。如下 reduce
函數將傳入的 values
進行計算和重組,返回一個 reduceVal
對象:
> var func_reduce2 = function(key, values){
reduceVal = {team: key, score: values, total: Array.sum(values), count: values.length};
return reduceVal;
};
複製代碼
reduceVal
對象中包含 team
、score
、total
和 count
四個屬性。但咱們還想爲其添加 avg
屬性,那麼能夠在 finallize
函數中執行 avg
值的計算和 avg
屬性的添加工做:
> var func_finalize = function(key, values){
values.avg = values.total / values.count;
return values;
};
複製代碼
map
保持不變,將這幾個函數做用於集合 mprds
上,對應示例以下:
> db.mprds.mapReduce(func_map, func_reduce2, {query: {team: "A"}, out: "mprds_result", finalize: func_finalize})
複製代碼
命令執行後,結果會存入指定的集合中。此時,集合 mprds_result
內容以下:
{ "_id" : 6, "value" : { "team" : 6, "score" : [ 9, 8 ], "total" : 17, "count" : 2, "avg" : 8.5 } }
{ "_id" : 24, "value" : 9 }
複製代碼
下圖描述了這次 mapReduce
操做的完整過程:
finallize
在
reduce
後面使用,微調
reduce
的處理結果。這着看起來像是一個園丁在修剪花圃的枝丫,因此人們將
finallize
形象地稱爲「剪枝」。
要注意的是:map
會將 key
值相同的文檔中的 value
概括到同一個對象中,這個對象會通過 reduce
和 finallize
。對於 key
值惟一的那些文檔,指定的 key
和 value
會被直接輸出。
除了 Aggregation Pipeline 和 Map-Reduce 這些複雜的聚合操做以外,MongoDB 還支持一些簡單的聚合操做,例如 count
、group
和 distinct
等。
count
count
用於計算集合或視圖中的文檔數,返回一個包含計數結果和狀態的文檔。其語法格式以下:
{
count: <collection or view>,
query: <document>,
limit: <integer>,
skip: <integer>,
hint: <hint>,
readConcern: <document>
}
複製代碼
count
支持的指令及對應描述以下:
指令 | 類型 | 描述 |
---|---|---|
count |
string | 要計數的集合或視圖的名稱,必填。 |
query |
document | 查詢條件語句。 |
limit |
integer | 指定要返回的最大匹配文檔數。 |
skip |
integer | 指定返回結果以前要跳過的匹配文檔數。 |
hint |
string or document | 指定要使用的索引,將索引名稱指定爲字符串或索引規範文檔。 |
假設要統計集合 mprds
中的文檔數量,對應示例以下:
> db.runCommand({count: 'mprds'})
{ "n" : 4, "ok" : 1 }
複製代碼
假設要統計集合 mprds
中 numb
爲 6
的文檔數量,對應示例以下:
> db.runCommand({count: 'mprds', query: {numb: {$eq: 6}}})
{ "n" : 2, "ok" : 1 }
複製代碼
指定返回結果以前跳過 1
個文檔,對應示例以下:
> db.runCommand({count: 'mprds', query: {numb: {$eq: 6}}, skip: 1})
{ "n" : 1, "ok" : 1 }
複製代碼
更多關於 count
的知識可查閱官方文檔 Count。
group
group
的做用是按指定的鍵對集合中的文檔進行分組,並執行簡單的聚合函數,它與 SQL 中的 SELECT ... GROUP BY
相似。其語法格式以下:
{
group:
{
ns: <namespace>,
key: <key>,
$reduce: <reduce function>,
$keyf: <key function>,
cond: <query>,
finalize: <finalize function>
}
}
複製代碼
group
支持的指令及對應描述以下:
指令 | 類型 | 描述 |
---|---|---|
ns |
string | 經過操做執行組的集合,必填。 |
key |
ducoment | 要分組的字段或字段,必填。 |
$reduce |
function | 在分組操做期間對文檔進行聚合操做的函數。 該函數有兩個參數:當前文檔和該組的聚合結果文檔。 必填。 |
initial |
document | 初始化聚合結果文檔, 必填。 |
$keyf |
function | 替代 key 。指定用於建立「密鑰對象」以用做分組密鑰的函數。使用 $keyf 而不是 key 按計算字段而不是現有文檔字段進行分組。 |
cond |
document | 用於肯定要處理的集合中的哪些文檔的選擇標準。 若是省略, group 會處理集合中的全部文檔。 |
finalize |
function | 在返回結果以前運行,此函數能夠修改結果文檔。 |
準備如下數據:
> db.sales.insertMany([
{_id: 1, orderDate: ISODate("2012-07-01T04:00:00Z"), shipDate: ISODate("2012-07-02T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香檳金"}},
{_id: 2, orderDate: ISODate("2012-07-03T05:20:00Z"), shipDate: ISODate("2012-07-04T09:00:00Z"), attr: {name: "高邦籃球鞋", price: 1999, size: 43, color: "獅王棕"}},
{_id: 3, orderDate: ISODate("2012-07-03T05:20:10Z"), shipDate: ISODate("2012-07-04T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香檳金"}},
{_id: 4, orderDate: ISODate("2012-07-05T15:11:33Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "極速跑鞋", price: 500, size: 43, color: "西湖藍"}},
{_id: 5, orderDate: ISODate("2012-07-05T20:22:09Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "新款椰子鞋", price: 2999, size: 42, color: "香檳金"}},
{_id: 6, orderDate: ISODate("2012-07-05T22:35:20Z"), shipDate: ISODate("2012-07-06T09:00:00Z"), attr: {name: "透氣網跑", price: 399, size: 38, color: "玫瑰紅"}}
])
複製代碼
假設要將集合 sales
中的文檔按照 attr.name
進行分組,並限定參與分組的文檔的 shipDate
大於指定時間。對應示例以下:
> db.runCommand({
group:{
ns: 'sales',
key: {"attr.name": 1},
cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
$reduce: function(curr, result){},
initial: {}
}
})
複製代碼
命令執行後,會返回一個結果檔。其中, retval
包含指定字段 attr.name
的數據,count
爲參與分組的文檔數量,keys
表明組的數量,ok
表明文檔狀態。結果文檔以下:
{
"retval" : [
{
"attr.name" : "高邦籃球鞋"
},
{
"attr.name" : "新款椰子鞋"
},
{
"attr.name" : "極速跑鞋"
},
{
"attr.name" : "透氣網跑"
}
],
"count" : NumberLong(5),
"keys" : NumberLong(4),
"ok" : 1
}
複製代碼
上方示例指定的 key
是 attr.name
。因爲參與分組的 5 個文檔中只有 2 個文檔的 attr.name
是相同的,因此分組結果中的 keys
爲 4
,這表明集合 sales
中的文檔被分紅了 4 組。
將 attr.name
換成 shipDate
,看看結果會是什麼。對應示例以下:
> db.runCommand(
{
group:{
ns: 'sales',
key: {shipDate: 1},
cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
$reduce: function(curr, result){},
initial: {}
}
}
)
複製代碼
命令執行後,返回以下結果:
{
"retval" : [
{
"shipDate" : ISODate("2012-07-04T09:00:00Z")
},
{
"shipDate" : ISODate("2012-07-06T09:00:00Z")
}
],
"count" : NumberLong(5),
"keys" : NumberLong(2),
"ok" : 1
}
複製代碼
因爲參與分組的 5 個文檔中有幾個文檔的 shipDate
是重複的,因此分組結果中的 keys
爲 2
,這表明集合 sales
中的文檔被分紅了 2 組。
上面的示例並無用到 reduce
、 initial
和 finallize
,接下來咱們將演示它們的用法和做用。假設要統計同組的銷售總額,那麼能夠在 reduce
中執行具體的計算邏輯。對應示例以下:
> db.runCommand(
{
group:{
ns: 'sales',
key: {shipDate: 1},
cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
$reduce: function(curr, result){
result.total += curr.attr.price;
},
initial: {total: 0}
}
}
)
複製代碼
命令執行後,返回結果以下:
{
"retval" : [
{
"shipDate" : ISODate("2012-07-04T09:00:00Z"),
"total" : 4998
},
{
"shipDate" : ISODate("2012-07-06T09:00:00Z"),
"total" : 3898
}
],
"count" : NumberLong(5),
"keys" : NumberLong(2),
"ok" : 1
}
複製代碼
人工驗證一下,發貨日期 shipDate
大於 2012-07-04T09:00:00Z
的文檔爲:
{ "_id" : 2, "orderDate" : ISODate("2012-07-03T05:20:00Z"), "shipDate" : ISODate("2012-07-04T09:00:00Z"), "attr" : { "name" : "高邦籃球鞋", "price" : 1999, "size" : 43, "color" : "獅王棕" } }
{ "_id" : 3, "orderDate" : ISODate("2012-07-03T05:20:10Z"), "shipDate" : ISODate("2012-07-04T09:00:00Z"), "attr" : { "name" : "新款椰子鞋", "price" : 2999, "size" : 42, "color" : "香檳金" } }
複製代碼
銷售總額爲 1999 + 2999 = 4998
,與返回結果相同。發貨日期 shipDate
大於 2012-07-06T09:00:00Z
的文檔爲:
{ "_id" : 4, "orderDate" : ISODate("2012-07-05T15:11:33Z"), "shipDate" : ISODate("2012-07-06T09:00:00Z"), "attr" : { "name" : "極速跑鞋", "price" : 500, "size" : 43, "color" : "西湖藍" } }
{ "_id" : 5, "orderDate" : ISODate("2012-07-05T20:22:09Z"), "shipDate" : ISODate("2012-07-06T09:00:00Z"), "attr" : { "name" : "新款椰子鞋", "price" : 2999, "size" : 42, "color" : "香檳金" } }
{ "_id" : 6, "orderDate" : ISODate("2012-07-05T22:35:20Z"), "shipDate" : ISODate("2012-07-06T09:00:00Z"), "attr" : { "name" : "透氣網跑", "price" : 399, "size" : 38, "color" : "玫瑰紅" } }
複製代碼
銷售總額爲 500 + 2999 + 399 = 3898
,與返回結果相同。
有時候可能須要統計每一個組的文檔數量以及計算平均銷售額,對應示例以下:
> db.runCommand(
{
group:{
ns: 'sales',
key: {shipDate: 1},
cond: {shipDate: {$gt: ISODate('2012-07-04T00:00:00Z')}},
$reduce: function(curr, result){
result.total += curr.attr.price;
result.count ++;
},
initial: {total: 0, count: 0},
finalize: function(result){
result.avg = Math.round(result.total / result.count);
}
}
}
)
複製代碼
上面的示例中改動了 $reduce
函數,目的是爲了統計 count
。而後新增了 finalize
,目的是計算分組中的平均銷售額。命令執行後,返回如下文檔:
{
"retval" : [
{
"shipDate" : ISODate("2012-07-04T09:00:00Z"),
"total" : 4998,
"count" : 2,
"avg" : 2499
},
{
"shipDate" : ISODate("2012-07-06T09:00:00Z"),
"total" : 3898,
"count" : 3,
"avg" : 1299
}
],
"count" : NumberLong(5),
"keys" : NumberLong(2),
"ok" : 1
}
複製代碼
以上就是 group
的基本用法和做用介紹,更多與 group
相關的知識可查閱官方文檔 group。
distinct
distinct
的做用是查找單個集合中指定字段的不一樣值,其語法格式以下:
{
distinct: "<collection>",
key: "<field>",
query: <query>,
readConcern: <read concern document>,
collation: <collation document>
}
複製代碼
distinct
支持的指令及對應描述以下:
指令 | 類型 | 描述 |
---|---|---|
distinct |
string | 集合名稱, 必填。 |
key |
string | 指定的字段, 必填。 |
query |
document | 查詢條件語句。 |
readConcern |
document | |
collation |
document |
準備如下數據:
> db.dress.insertMany([
... {_id: 1, "dept": "A", attr: {"款式": "立領", color: "red" }, sizes: ["S", "M" ]},
... {_id: 2, "dept": "A", attr: {"款式": "圓領", color: "blue" }, sizes: ["M", "L" ]},
... {_id: 3, "dept": "B", attr: {"款式": "圓領", color: "blue" }, sizes: "S" },
... {_id: 4, "dept": "A", attr: {"款式": "V領", color: "black" }, sizes: ["S" ] }
])
複製代碼
假設要統計集合 dress
中全部文檔的 dept
字段的不一樣值,對應示例以下:
> db.runCommand ( { distinct: "dress", key: "dept" } )
{ "values" : [ "A", "B" ], "ok" : 1 }
複製代碼
或者看看有那些款式,對應示例以下
> db.runCommand ( { distinct: "dress", key: "attr.款式" } )
{ "values" : [ "立領", "圓領", "V領" ], "ok" : 1 }
複製代碼
就算值是數組, distinct
也能做出正確處理,對應示例以下:
> db.runCommand ( { distinct: "dress", key: "sizes" } )
{ "values" : [ "M", "S", "L" ], "ok" : 1 }
複製代碼
以上就是本篇對 MongoDB 中流式聚合操做的介紹。聚合與管道的概念並不常見,可是理解起來也不難。只要跟着示例思考,並動手實踐,相信你很快就可以熟練掌握聚合操做。
除了幫助你掌握流式聚合操做以外,我還寫了一整套 MongoDB 快速入門教程,看完教程你將收穫:
這是寫給 0 基礎同窗的 MongoDB 快速入門文章。內容從文檔 CRUD 到流式聚合操做;從執行計劃、索引、數據模型到複製集;從分片、訪問控制到數據備份與還原。全篇近 5 萬詞的內容覆蓋了 MongoDB 的大部分知識點,徹底知足平常開發的要求。
先聲明,這套教程 9.9 元。
你能夠添加做者微信:DomFreez,並向做者吐槽。
目前已有 600+ 朋友參與了哦