MongoDB聚合管道
使用聚合管道可以對集合中的文檔進行變換群組合。 管道是由一個個功能節點組成的,這些節點用管道操作符來進行表示。聚合管道以一個集合中的所有文檔作為開始,然後這些文檔從一個操作節點流向下一個節點 ,每個操作節點對文檔做相應的操作。這些操作可能會建立新的文檔或者過濾掉一些不符合條件的文檔,在管道中可以對文檔進行重複操作。 管道表達式隻可以操作目前管道中的文檔,不能通路其他的文檔:表達式操作可以在記憶體中完成對文檔的轉換。
文法格式:
db.runCommand({
aggregate: "<collection>",
pipeline: [ <stage>, <...> ],
explain: <boolean>,
allowDiskUse: <boolean>,
cursor: <document>,
bypassDocumentValidation: <boolean>
})
//或
db.collection.aggregate([ <pipeline>, <...> ], options)
參數說明:
操作符 | 描述 |
aggregate | 要聚合的集合名稱 |
pipeline | 管道操作符 |
explain | 傳回指定aggregate各個階段管道的執行計劃資訊 |
allowDiskUse | 每個階段管道限制為100MB的記憶體,如果大于100MB的資料可以先寫入臨時檔案。設定為true時,aggregate操作可時可以先将資料寫入對應資料目錄的子目錄中 的唯一并以_tmp結尾的文檔中。 |
cursor | 指定遊标的初始批批大小。光标的字段的值是一個與場batchSize檔案。 } |
bypassDocumentValidation | 隻有當你指定了$out操作符,使db.collection.aggregate繞過文檔驗證操作過程中。這讓您插入不符合驗證要求的文檔。 |
管道操作符:
$project | 資料投影,主要用于重命名、增加和删除字段 |
$match | 過濾操作,篩選符合條件文檔,作為下一階段的輸入 $match的文法和查詢表達式db.collection.find() 的文法相同 注意:1. 不能在$match操作符中使用$where 表達式操作符。 2. $match盡量出現在管道的前面,這樣可以提早過濾文檔,加快聚合速度。 3. 如果$match出現在最前面的話,可以使用索引來加快查詢。 |
$limit | 限制經過管道的文檔數量 $limit的參數隻能是一個正整數 |
$skip | 從待操作集合開始的位置跳過文檔的數目 $skip參數也隻能為一個正整數 |
$unwind | 将數組分解為單個的元素,并與文檔的其餘部分一同傳回 注意:1.如果$unwind目标字段不存在,則整個文檔都會被忽略過濾掉 2.如果$unwind目标字段不是一個數組,則會報錯 3.如果$unwind目标字段數組為空,則該文檔也會被忽略過濾掉 |
$group | 可以将文檔依據指定字段的不同值進行分組,如果標明了需要進行分組的字段,就可以将指定的字段傳遞給$group函數的_id字段注意:1.$group的輸出是無序的。 2.$group操作預設實在記憶體中進行的,超過此限制會報錯,若要允許處理大型資料集,allowDiskUse 将選項設定為啟用 $group 操作真實寫入臨時檔案。具體請參考 官方文檔 |
$sort | 對文檔按照指定字段排序 注意:1.如果将$sort放到管道前面的話可以利用索引,提高效率 2.在管道中如果$sort出現在$limit之前的話,$sort隻會對前$limit個文檔進行操作,這樣在記憶體中也隻會保留前$limit個文檔,進而可以極大的節省記憶體 3.$sort操作符預設在記憶體中進行,,超過此限制會報錯,若要允許處理大型資料集,allowDiskUse 将選項設定為啟用 $group 操作真實寫入臨時檔案。具體請參考 |
$geoNear | 會傳回一些坐标值,這些值以按照距離指定點距離由近到遠進行排序 |
$sample | 從待操作的集合中随機傳回指定數量的文檔 注意:如果指定的數量 N 大于等于集合文檔總數的5%,$sample 執行集合掃描,執行排序,然後選擇前 N 的文檔(受排序的記憶體限制) 如果 N 是小于 5%的集合中的文檔總數 如果使用 WiredTiger 存儲引擎,$sample 使用僞随機遊标在抽樣 N 文檔集合。 如果使用 MMAPv1 存儲引擎,$sample 使用 _id 索引随機選擇 N 個文檔。 |
$lookup | 用于與統一資料庫中其他集合之間進行join操作 |
$out | 使用者将聚合的結果輸出到指定的集合,如果要使用$out則必須在整個管道操作的最後階段 如果指定的集合尚不存在,$out 操作會在目前資料庫中建立一個新的集合。集合不是可見的直到聚合完成。如果聚合失敗,MongoDB 不會建立集合。 如果集合指定的 $out 操作已經存在,然後完成後的聚合,$out 階段以原子方式以新的結果集合替換現有集合的,$out 操作不會更改任何存在于以前的集合的索引。如果聚合失敗$out 則不會對現有集合做任何更改。 |
$redact | 字段所處的document結構的級别. $redact還有三個重要的參數: 1)$$DESCEND: 傳回包含目前document級别的所有字段,并且會繼續判字段包含内嵌文檔,内嵌文檔的字段也會去判斷是否符合條件。 2)$$PRUNE:傳回不包含目前文檔或者内嵌文檔級别的所有字段,不會繼續檢測此級别的其他字段,即使這些字段的内嵌文檔持有相同的通路級别。 3)$$KEEP:傳回包含目前文檔或内嵌文檔級别的所有字段,不再繼續檢測此級别的其他字段,即使這些字段的内嵌文檔中持有不同的通路級别。 |
聚合管道操作執行個體
提取字段
db.order.aggregate({ $project: { cust_id: 1, price: 1 } });
db.order.aggregate({ $project: { items: 1, items: { sku: 1 } } });
重命名字段
db.order.aggregate({ $project: { "orderid": '$_id', _id: 0, "custid": '$cust_id' } });
db.order.aggregate({ $project: { items: { '_sku': '$sku', sku: 1 } } })
建立文檔
db.order.aggregate({
$project: {
price: 1,
details: { price: '$items.price' }
}
});
注意:由于對字段進行重命名時,MongoDB并不會記錄字段的曆史名稱,是以如果針對原來的字段建立過索引,那麼聚合管道在進行排序時無法在下面的排序操作時使用索引, 應當盡量在修改字段名稱之前使用排序
db.order.aggregate([{ $project: { "orderid": '$_id', _id: 0, "custid": '$cust_id' } }, { $sort: { custid: -1 } }])
//建議寫法
db.order.aggregate([{ $sort: { cust_id: -1 } }, { $project: { "orderid": '$_id', _id: 0, "custid": '$cust_id' } }])
{ $add: [ <expression1>, <expression2>, ... ] } 将多個數字或日期進行相加,隻支援數字和時間格式,如果其中一個參數是日期,$add會将其他參數視為要添加到日期的毫秒
db.order.aggregate({
$project: {
"add": {
"$add": ["$price", 1]
}
}
})
{ $subtract: [ <expression1>, <expression2> ] } 接受兩個表達式作為參數,使用第一個表達式減去第二個表達式作為結果
db.order.aggregate({
$project: {
"subtract": {
"$subtract": [{ "$add": ["$price", 12] }, 10]
}
}
})
{ $multiply: [ <expression1>, <expression2>, ... ] } 接受一個或多個表達式,并将它們相乘
db.order.aggregate({
$project: {
"multiply": { "$multiply": ["$price", 3] }
}
})
{ $divide: [ <expression1>, <expression2> ] } 接受兩個表達式作為參數,使用第一個表達式除以第二個表達式的商作為結果
db.order.aggregate({
$project: {
"divide": { "$divide": ["$price", 3] }
}
})
更多數學表達式參考官方文檔 https://docs.mongodb.com/manual/reference/operator/aggregation-arithmetic/
{ $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case-> } } 或{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }如果boolean-expression為true則執行true-case否則執行false-case
db.order.aggregate({
$project: {
"price": {
"$cond": { if: { $gt: ["$price", 25] }, then: true, else: false }
}
}
});
//或
db.order.aggregate({
$project: {
"price": {
"$cond": [{ $gt: ["$price", 25] }, true, false]
}
}
});
{ $ifNull: [ , ] } 如果expression 為 null 則傳回replacement-expression-if-null 否則傳回expression的值
db.order.aggregate({
$project: {
"total": {
"$ifNull": ["$total", 0]
}
}
})
{ $and: [ <expression1>, <expression2>, ... ] } 如果所有表達式都傳回true,則結果為true,否則為false 除了布爾類型false,以下幾種類型也為false,如:null,0,undefined ;其它值則為true,包括非0的值及數組
//Example Result
{ $and: [1, "green"] } true
{ $and: [] } true
{ $and: [[null], [false], [0]] } true
{ $and: [null, true] } false
{ $and: [0, true] } false
{ $or: [ <expression1>, <expression2>, ... ] } 隻要有任意表達式為true,則結果為true,否則為false 除了布爾類型false,以下幾種類型也為false,如:null,0,undefined ;其它值則為true,包括非0的值及數組
//Example Result
{ $or: [ true, false ] } true
{ $or: [ [ false ], false ] } true
{ $or: [ null, 0, undefined ] } false
{ $or: [] } false
{ $not: [ <expression> ] } 對expression取反 除了布爾類型false,以下幾種類型也為false,如:null,0,undefined ;其它值則為true,包括非0的值及數組
//total不存在為false,取反結果則為true
db.order.aggregate({
$project: {
not: {
$not: "$total"
}
}
})
$match 過濾操作,篩選符合條件文檔,作為下一階段的輸入 $match的文法和查詢表達式db.collection.find() 的文法相同
db.order.aggregate([{ $match: { cust_id: "1" } }, {
$project: {
"total": {
"$ifNull": ["$total", 0]
}
}
}])
$out 使用者将聚合的結果輸出到指定的集合
db.order.aggregate([{ $match: { cust_id: "1" } }, {
$project: {
"total": {
"$ifNull": ["$total", 0]
}
}
},
{ $out: "testaggregate" }])
$unwind 将數組分解為單個的元素,并與文檔的其餘部分一同傳回
db.order.aggregate({$unwind:"$items"})
$group 對資料進行分組 $group的時候必須要指定一個_id域,同時也可以包含一些算術類型的表達式操作符
db.order.aggregate([
{
$unwind: "$items"
},
{
$group: {
_id: "$cust_id",
qty: {
$sum: "$items.qty"
}
}
}
])
關系型資料庫與MongoDB關于聚合的一些對比
關系型資料庫 | Mongodb |
WHERE | |
GROUP BY | |
HAVING | |
SELECT | |
ORDER BY | |
LIMIT | |
SUM() | $sum |
COUNT() | |
join |