Aggregation
- 聚合操作處理數據記錄並返回計算結果
- 聚合操作將來自多個文檔的值進行分組,對分組的數據進行各種操作並返回單個結果
- mongodb 提供了三種進行聚合操作的方法:聚合管道、map-reduce函數、single purpose 聚合
Aggregation Pipeline
-
mongodb 的聚合框架是基於數據處理管道的概念建模的,文檔通過一個多階段管道處理,轉換爲聚合結果
-
聚合管道使用本地操作實現了高效的數據聚合操作,是mongodb首選的數據聚合方法
-
聚合管道可以對分片集合進行操作
-
在聚合管道的某些階段,可以使用索引來提高性能。此外,聚合管道有一個內部優化階段
-
聚合管道由多個階段(stage)組成,每個階段都會對輸入文檔進行處理轉換。管道階段不需要爲每個輸入文檔生成一個輸出文檔,因爲有些階段會生成新的文檔或過濾掉文檔
-
管道階段可以在管道中出現多次,但 $out、$merge、$geoNear 階段只能出現一次
db.collection.aggregate
-
計算集合或視圖中數據的聚合結果
-
遊標
-
聚合返回的遊標只支持對已計算的遊標進行操作的方法
https://docs.mongodb.com/v4.2/reference/method/db.collection.aggregate/#cursor-behavior
Cursors returned from aggregation only supports cursor methods that operate on evaluated cursors (i.e. cursors whose first batch has been retrieved)
cursor.hasNext()
cursor.next()
cursor.toArray()
cursor.forEach()
cursor.map()
cursor.objsLeftInBatch()
cursor.itcount()
cursor.pretty() -
在 mongo shell 中,如果
aggregate()
方法返回的遊標沒有使用 var 關鍵字分配給一個變量,那麼 mongo shell 將自動迭代遊標20次
-
-
會話
- 從 mongodb 3.6 開始,mongodb驅動和mongo shell 將所有操作與一個服務器會話關聯,處理未確認的寫操作
- 如果一個會話空閒時間超過30分鐘,則mongodb服務器會將其標記爲過期,並可能在任何時候關閉。mongodb服務器關閉會話的時候會終止任何正在進行的操作,並打開與會話關聯的遊標
- 在會話內創建的遊標,不能在會話外調用
getMore
- 在會話外創建的遊標,不能在會話內調用
getMore
格式
db.collection.aggregate(pipeline, options)
-
pipeline
- 類型:數組
- 描述:
- 聚合操作列表
- 可以接收單個階段而非數組,但是非數組類型無法指定 options 參數
-
options
-
類型:Document
-
描述:aggregate() 方法傳遞給 aggregate 命令的額外選項,僅當pipeline爲數組時可用
-
explain
- 類型:布爾
- 描述
- 指定管道處理的返回信息
-
allowDiskUse
-
類型:布爾
-
描述:是否允許使用臨時文件
-
true
聚合操作可以將數據寫入臨時文件,位於
dbPath
目錄中的子目錄_tmp
但 $graphLookup、$addToSet、$push 階段除外
-
-
-
cursor
- 類型:Document
- 描述:指定遊標的初始批處理大小
-
maxTimeMS
- 類型:非負整數(單位 毫秒)
- 描述
- 指定處理遊標操作的時間限制
- 如果沒有指定,則操作不會超時
- 值0,顯式指定默認的無限制行爲
- mongodb 使用與
db.killOp()
方法相同的機制終止超時的操作。mongodb 只在一個指定的中斷點終止一個操作
-
bypassDocumentValidation
- 類型:布爾
- 描述
- 僅當指定 $out 或 $merge 階段時適用
- 使
aggregate()
方法繞過文檔數據校驗,允許管道處理階段插入不滿足數據校驗的文檔
-
readConcern
-
類型:Document
-
描述
-
指定讀取策略
-
格式
readConcern: { level : <value> }
- value 取值
- "local"
- "available"
- "majority"
- "linearizable"
- value 取值
-
-
-
collation
- 類型:Document
- 描述
- 指定排序規則
-
hint
- 類型:字符串或文檔
- 描述
- 指定聚合操作使用的索引
- 可以通過索引名稱或索引規範文檔指定
-
comment
- 類型:字符串
- 描述
- 指定字符串來幫助追蹤操作
- 可以在 comment 中編碼任意信息,以便更容易地通過系統跟蹤或識別特定的操作,例如包含進程ID、線程ID、客戶端主機名、發出命令的用戶等
-
可通過 database profiler、currentOp、logs 追蹤
-
writeConcern
- 類型:Document
- 描述:指定 $out、$merge 階段的寫入策略
-
-
Returns
- 遊標,指向聚合管道最後階段生成的文檔
- 遊標,如果包含 explain 選項,則指向關於聚合操作處理的詳細信息的文檔
- 空遊標,如果管道中包含 $out 操作
示例
文檔
db.orders.insertMany([
{ _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 },
{ _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 },
{ _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 },
{ _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 },
{ _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }
])
-
group and sum
> var res = db.orders.aggregate([ { $match: { status: "A" } }, { $group: { _id: "$cust_id", total: { $sum: "$amount" } } }, { $sort: { total: -1 } } ]) > res { "_id" : "xyz1", "total" : 100 } { "_id" : "abc1", "total" : 75 }
- 選擇狀態爲A的文檔 -->
- 按 cust_id 字段對匹配的文檔進行分組,並計算每組中 amount 字段的總和 -->
- 按 total 字段對結果進行降序排列
-
顯示聚合管道執行計劃的詳細信息
db.orders.explain().aggregate([ { $match: { status: "A" } }, { $group: { _id: "$cust_id", total: { $sum: "$amount" } } }, { $sort: { total: -1 } } ])
-
使用外部存儲處理大數據集
var results = db.stocks.aggregate( [ { $project : { cusip: 1, date: 1, price: 1, _id: 0 } }, { $sort : { cusip : 1, date: 1 } } ], { allowDiskUse: true } )
-
指定聚合操作使用的索引
db.foodColl.createIndex( { qty: 1, type: 1 } ); db.foodColl.createIndex( { qty: 1, category: 1 } );
db.foodColl.aggregate( [ { $sort: { qty: 1 }}, { $match: { category: "cake", qty: 10 } }, { $sort: { type: -1 } } ], { hint: { qty: 1, category: 1 } } )
Pipeline Expressions
管道表達式
https://docs.mongodb.com/v4.2/core/aggregation-pipeline/#pipeline-expressions
for update
https://docs.mongodb.com/v4.2/tutorial/update-documents-with-aggregation-pipeline/
for Sharded Collections
vs Map-Reduce
- 聚合管道是 map-reduce 的一種替代方案,對於複雜的聚合任務是首選解決方案
限制
-
聚合管道對值類型和結果大小有一些限制
-
結果大小限制
-
aggregate 命令
-
聚合命令可以返回遊標,也可以將結果存儲在集合中,結果集中的每個文檔都受BSON文檔大小限制,當前爲 16MB,如果某個文檔大小超過BSON大小限制,則聚合命令將產生錯誤
- 僅適用於結果集中返回的文檔,管道中的文檔不受此限制
- MongoDB 3.6刪除了聚合命令以單個文檔的形式返回結果的選項
-
-
aggregate() 方法
- 返回遊標
-
-
內存大小限制
- 聚合管道每個階段可以利用的RAM大小爲 100MB,超過內存限制則會產生錯誤
- 對於大型數據集,可以在 aggregate() 方法中設置 allowDiskUse 選項,允許聚合管道操作鍵該數據寫入臨時文件中
- 以下聚合操作只能使用內存
- $graphLookup
- $addToSet
- push
- 如果聚合管道的某個階段設置了 allowDiskUse:true 則對其他階段也生效
優化
聚合命令對單個集合進行操作,邏輯上將整個集合傳遞給聚合管道,爲了優化操作,應儘可能比表面掃描整個集合
-
利用索引
mongodb的查詢規劃器(query planner)分析聚合管道,以確定是否可以使用索引來提高某些階段的性能
- $match
- 如果 $match 處於管道開頭,則可利用索引篩選文檔,減少掃描文檔數量
- $sort
- 只要 $sort 前面沒有 $project、$unwind、$group 階段,則可利用索引排序
- $group
- 滿足以下條件,則 $group 可以利用索引查找每個分組中的第一個文檔
- 在 $sort 階段之後,且 $sort 階段對分組字段進行排序
- 在分組字段上有一個索引,與 $sort 排序一致
- 在 $group 階段中只使用 $first 累加器
- 滿足以下條件,則 $group 可以利用索引查找每個分組中的第一個文檔
- $geoNear
- $match
-
預先過濾
-
當聚合操作只需針對集合中數據的一個子集,則在管道開頭使用 $match、$limit、$skip 等階段,限制輸入文檔的數量
-
在管道開頭使用 $match 和 $sort 階段,邏輯上相當於一個帶有排序的查詢,並且可以使用索引,如果可能,儘量在管道開頭使用 $match 階段
-
Stages
$group
-
按指定字段對集合中的文檔進行分組,每組輸出一個文檔,輸出文檔的
_id
字段包含唯一值 -
類似 sql 中的
GROUP BY
-
輸出文檔中還可以添加自定義字段,用於顯示累加器表達式值
-
如果文檔不包含分組字段,則忽略該文檔
區別於包含分組字段,但是值爲null
格式
db.collection.aggregate([
{
$group:
{
_id: <expression>,
<field>: { <accumulator> : <expression> },
...
}
}
])
-
_id
- 類型:表達式(字符串
"$<分組字段>"
或 操作符表達式) - 描述:指定分組字段
- 如果指定爲 null 或 其他常量值(數字),則將整個集合視爲一組進行計算
- 類型:表達式(字符串
-
field
- 類型:字符串
- 描述:自定義字段,用於顯示累加器表達式的值
-
<accumulator>
-
描述:累加器(聚合)操作符
-
常用聚合操作符
操作符 描述 $sum 利用 $group 分組後,對同組內的文檔,對指定字段的數值進行求和 $avg 利用 $group 分組後,對同組內的文檔,對指定字段的數值求平均值 $first 利用 $group 分組後,對同組內的文檔,顯示指定字段的第一個值 $last 利用 $group 分組後,對同組內的文檔,顯示指定字段的最後一個值 $max 利用 $group 分組後,對同組內的文檔,顯示指定字段的最大值 $min 利用 $group 分組後,對同組內的文檔,顯示指定字段的最小值 $push 利用 $group 分組後,對同組內的文檔,以數組的方式顯示指定字段 $addToSet 利用 $group 分組後,對同組內的文檔,以數組的方式顯示字段不重複的值
-
-
<expression>
- 類型:表達式(字符串
"$<計算的字段>"
或 操作符表達式) - 描述:累加器操作符計算的字段
- 類型:表達式(字符串
示例
文檔
db.sales.insertMany([
{ "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
{ "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
{ "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
{ "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
{ "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
{ "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
{ "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
{ "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
])
-
計算集合中文檔的數量
db.sales.aggregate( [ { $group: { _id: null, count: { $sum: 1 } } } ] )
-
返回
{ "_id" : null, "count" : 8 }
-
-
_id
設爲 null 或 常量db.sales.aggregate( [ { $group: { _id: 404, count: { $sum: 1 } } } ] )
-
返回
{ "_id" : 404, "count" : 8 }
-
-
檢索某個字段的不同值
db.sales.aggregate( [ { $group : { _id : "$item" } } ] )
-
having
db.sales.aggregate( [ // First Stage { $group : { _id : "$item", totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } } } }, // Second Stage { $match: { "totalSaleAmount": { $gte: 100 } } } ] )
- 按 item 字段進行分組
- 計算每組的總銷售額
- 返回總銷售額大於等於100的文檔
-
多聚合操作符
db.sales.aggregate([ // First Stage { $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } } }, // Second Stage { $group : { // 多個 key-value 鍵值對 _id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }, averageQuantity: { $avg: "$quantity" }, count: { $sum: 1 } } }, // Third Stage { $sort : { totalSaleAmount: -1 } } ])
- 選擇 日期範圍是 2014 年的文檔
- 按照日期進行分組,每組計算銷售總額、平均值以及文檔數量
- 按照銷售總額倒序排列
$project
-
顯示並傳遞指定字段,包括文檔中的現有字段或新計算的字段(新增)
-
對於嵌入式文檔中的字段,可以通過 點表示法 或 嵌套字段表示法
"contact.address.country": <1 or 0 or expression> contact: { address: { country: <1 or 0 or expression> } }
-
如果 $project 指定一個空文檔則報錯
格式
{ $project: { <specification(s)> } }
-
specifications
-
_id : <0 or false>
- 排除
_id
字段
- 排除
-
<field> : <0 or 1>
-
1:包含指定字段
_id
字段默認顯示和傳遞,其他字段默認不顯示並排除- 如果指定的字段不存在, $project 將忽略該字段(不會創建)
- 不能同時指定包含和排除字段
-
0:排除指定字段
-
如果有條件的排除,需使用 REMOVE 變量
-
如果排除
_id
以外的所有字段,則不能使用任何其他的規範表單if you exclude fields, you cannot also specify the inclusion of fields, reset the value of existing fields, or add new fields.
-
除
_id
字段外,其他字段默認不顯示,但如果顯式排除了某個字段,則其他所有字段將顯示並傳遞
-
-
-
<field> : <expression>
- 增加一個新字段或重置已存在的字段
-
-
常用操作符
- 字符串操作符
- 算數運算操作符
- 時間日期操作符
示例
-
排除嵌入式文檔中的指定字段
文檔
db.books.insert({ "_id" : 1, title: "abc123", isbn: "0001122223334", author: { last: "zzz", first: "aaa" }, copies: 5, lastModified: "2016-07-28" })
排除
db.books.aggregate( [ { $project : { "author.first" : 0, "lastModified" : 0 } } ] ) db.bookmarks.aggregate( [ { $project: { "author": { "first": 0}, "lastModified" : 0 } } ] )
-
包含嵌入式文檔中的指定字段
文檔
db.bookmarks.insertMany([ { _id: 1, user: "1234", stop: { title: "book1", author: "xyz", page: 32 } }, { _id: 2, user: "7890", stop: [ { title: "book2", author: "abc", page: 5 }, { title: "book3", author: "ijk", page: 100 } ] } ])
包含
db.bookmarks.aggregate( [ { $project: { "stop.title": 1 } } ] ) db.bookmarks.aggregate( [ { $project: { stop: { title: 1 } } } ] )
-
結果
{ "_id" : 1, "stop" : { "title" : "book1" } } { "_id" : 2, "stop" : [ { "title" : "book2" }, { "title" : "book3" } ] }
-
多個值將自動以數組的形式返回
-
-
新增字段
db.books.aggregate( [ { $project: { title: 1, isbn: { prefix: { $substr: [ "$isbn", 0, 3 ] }, group: { $substr: [ "$isbn", 3, 2 ] } }, lastName: "$author.last", copiesSold: "$copies" } } ] )
-
結果
{ "_id" : 1, "title" : "abc123", "isbn" : { "prefix" : "000", "group" : "11" }, "lastName" : "zzz", "copiesSold" : 5 }
-
-
新增數組
文檔
{ "_id" : ObjectId("55ad167f320c6be244eb3b95"), "x" : 1, "y" : 1 }
新增
db.collection.aggregate( [ { $project: { myArray: [ "$x", "$y", "$someField" ] } } ] )
-
結果
{ "_id" : ObjectId("55ad167f320c6be244eb3b95"), "myArray" : [ 1, 1, null ] }
-
如果數組中引用了不存在的字段,則用 null 代替
-
$sort
- 對所有的輸入文檔進行排序,返回排序後的文檔
格式
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
- <sort order>
- 1 :升序排序
- -1:降序排列
- 多字段排序,從左到右計算排序順序,先按 field1 排序,具有相同 field1 值的文檔再按 field2 排序
優化
-
當 $sort 在 $limit 之前且中間沒有改變文檔數量的操作時,優化器可以將 $limit 合併到 $sort 中,即 $sort 操作在進行過程中只維護頂部的 n 個結果(n 爲 $limit 限定的值),mongodb 只在內存中存儲 n 個文檔
-
$sort 階段只能佔用 100 MB 的內存,默認超過則報錯。爲了處理大型數據集,設置 allowDiskUse 爲 true,允許 $sort 操作將數據寫入臨時文件
-
如果管道前面沒有 $project、$unwind、$group 階段,則$sort 可利用索引進行排序
$skip
- 跳過指定數量的文檔,返回剩餘的文檔
- 對文檔內容無影響
格式
{ $skip: <positive integer> }
- 值爲一個正整數
$limit
- 限制返回的文檔數量,返回指定數量的文檔(按輸入順序)
- 對文檔內容無影響
格式
{ $limit: <positive integer> }
- 值爲一個正整數
$match
- 篩選文檔,返回符合條件的文檔
- 儘可能在管道開頭放置 $match 階段,限制輸入文檔數量
- 管道開頭的 $match 可以像
find()
、findOne()
那樣利用索引
格式
{ $match: { <query> } }
- query:查詢條件表達式
限制
-
$match 查詢語法與讀取操作查詢語法相同;例如:$match不接受原始聚合表達式。要在$match中包含聚合表達式,請使用$expr查詢表達式
{ $match: { $expr: { <aggregation expression> } } }
-
要在 $match 中使用 $text,則必須作爲管道的第一階段
示例
-
相等匹配
db.articles.aggregate( [ { $match : { author : "dave" } } ] );
-
條件查詢
db.articles.aggregate( [ { $match: { $or: [ { score: { $gt: 70, $lt: 90 } }, { views: { $gte: 1000 } } ] } }, { $group: { _id: null, count: { $sum: 1 } } } ] );
-
$match 選擇分數大於70小於90的文檔,或者視圖大於等於1000的文檔,這些文檔通過管道傳輸給 $group 階段
-
$group 統計文檔數量
-
結果
{ "_id" : null, "count" : 5 }
-
$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>
}
}
-
from
- 類型:字符串
- 指定要與輸入集合進行左外聯的同一數據庫中的其他集合
-
localField
- 類型:字符串
- 管道輸入集合中需關聯的鍵
- $lookup 將 localField 和 foreignField 進行相等匹配
- 如果輸入集合中不包含 localField 指定的字段,則視 localField 值爲 null 與 foreignField 進行相等匹配
- 如果localField 字段的類型爲數組,則數組元素依次匹配 foreignField
-
foreignField
- 類型:字符串
- from 集合中需關聯的鍵
- $lookup 將 foreignField 和 localField 進行相等匹配
- 如果 from 集合中不包含 foreignField 指定的字段,則視 foreignField 值爲 null 與 localField 進行相等匹配
-
as
- 類型:字符串
- 指定要添加到輸入文檔中的新數組字段的名稱
- 該字段包含 from 集合中符合條件的文檔
- 如果該字段名稱已經存在,則會覆蓋現有字段
示例
-
localField爲單一值
文檔
db.orders.insert([ { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 }, { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 }, { "_id" : 3 } ]) db.inventory.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 } ])
通過 orders 集合中的 item 字段和 inventory 集合彙總的 sku 字段,將兩個集合連接起來
db.orders.aggregate([ { $lookup: { from: "inventory", localField: "item", foreignField: "sku", as: "inventory_docs" } } ])
-
查詢 orders 集合中 item 字段的值與 inventory 集合中 sku 字段的值 相等的 文檔
-
輸出文檔中的 inventory_docs 字段包含 inventory 集合中符合條件的文檔
-
結果
{ "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2, "inventory_docs" : [ { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 } ] } { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1, "inventory_docs" : [ { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 } ] } { "_id" : 3, "inventory_docs" : [ { "_id" : 5, "sku" : null, "description" : "Incomplete" }, { "_id" : 6 } ] }
orders 集合中的
{ "_id" : 3 }
文檔不包含 item 字段,則$lookup將其視爲 null 匹配 inventory 集合中的{ "_id" : 5, "sku" : null, "description" : "Incomplete" }
和{ "_id" : 6 }
兩個文檔
-
-
localFIeld 爲數組
文檔
db.classes.insert( [ { _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] }, { _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] } ]) db.members.insert( [ { _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" }, { _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" }, { _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" }, { _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" }, { _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" }, { _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" } ])
通過 classes 集合中的 enrollmentlist 字段 和 members 集合中的 name 字段,將兩個集合連接起來
db.classes.aggregate([ { $lookup: { from: "members", localField: "enrollmentlist", foreignField: "name", as: "enrollee_info" } } ])
-
查詢 classes 集合中 enrollmentlist 數組中的元素跟 members 集合中 name 字段值相等的文檔
-
輸出文檔中的 enrollee_info 字段包含 members 集合中符合條件的文檔
-
結果
{ "_id" : 1, "title" : "Reading is ...", "enrollmentlist" : [ "giraffe2", "pandabear", "artie" ], "days" : [ "M", "W", "F" ], "enrollee_info" : [ { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" }, { "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" }, { "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" } ] } { "_id" : 2, "title" : "But Writing ...", "enrollmentlist" : [ "giraffe1", "artie" ], "days" : [ "T", "F" ], "enrollee_info" : [ { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" }, { "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" } ] }
-
不相關子查詢與多條件查詢
{
$lookup:
{
from: <collection to join>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to execute on the collection to join> ],
as: <output array field>
}
}
-
from
- 類型:字符串
- 指定要與輸入集合進行左外聯的同一數據庫中的其他集合
-
left
- 類型:文檔
let : { <引用變量名> : "$<輸入文檔中的字段>" }
- 使用變量表達式訪問輸入文檔中的字段
- 定義要在 pipeline 管道階段中使用的變量,用以訪問輸入 $lookup 階段的文檔中的字段
- 類型:文檔
-
pipeline
-
類型:數組
-
指定要在 from 集合上運行的管道,用以篩選符合條件的文檔
如要返回所有的文檔,則指定一個不含任何階段的空管道
[ ]
-
pipeline 管道中不能包含 $out 和 $merge 階段
-
pipeline 管道階段 能 直接訪問 from 集合中的文檔字段,通過
"$<from集合中的文檔字段>"
-
pipeline 管道階段 不能 直接訪問輸入文檔中的字段,必須首先在 let 子句中定義中間變量,然後才能在 pipeline 管道的各個階段中引用
- 在 pipeline 管道階段中通過
$$<variable>
的形式引用變量 - $match 階段需要通過 $expr 操作符來使用聚合表達式,訪問 let 子句中定義的變量
- 在 pipeline 管道階段中通過
-
-
as
- 類型:字符串
- 指定要添加到輸入文檔中的新數組字段的名稱
- 該字段包含 from 集合中符合條件的文檔
- 如果該字段名稱已經存在,則會覆蓋現有字段
示例
-
多條件查詢
文檔
db.orders.insert([ { "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 }, { "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1 }, { "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60 } ]) db.warehouses.insert([ { "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 }, { "_id" : 2, "stock_item" : "pecans", warehouse: "A", "instock" : 80 }, { "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 }, { "_id" : 4, "stock_item" : "cookies", warehouse: "B", "instock" : 40 }, { "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 } ])
通過 item字段 以及 條件(庫存數量是否滿足訂單數量),將orders集合和warehouses集合連接起來
db.orders.aggregate([ { $lookup: { from: "warehouses", let: { order_item: "$item", order_qty: "$ordered" }, pipeline: [ { $match: { $expr: { $and: [ { $eq: [ "$stock_item", "$$order_item" ] }, { $gte: [ "$instock", "$$order_qty" ] } ] } } }, { $project: { stock_item: 0, _id: 0 } } ], as: "stockdata" } } ])
-
根據 orders 集合中的 name 字段,查詢 warehouses 集合中庫存數量滿足訂單數量的文檔
-
輸出文檔中的 stockdata 字段包含符合條件的 warehouses 集合中的文檔
-
結果
{ "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2, "stockdata" : [ { "warehouse" : "A", "instock" : 120 }, { "warehouse" : "B", "instock" : 60 } ] } { "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1, "stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] } { "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60, "stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] }
-
-
不相關子查詢
子查詢或內部查詢
- 嵌套在其它查詢中的查詢
主查詢或外部查詢
- 包含子查詢的查詢
不相關子查詢
- 內部查詢的執行獨立於外部查詢,內部查詢只執行一次,然後將結果作爲外部查詢的條件
相關子查詢
- 內部查詢的執行依賴於外部查詢的數據,外部查詢每執行一次,內部查詢也會執行一次。
- 每次都是外部查詢先執行,將當前查詢數據傳遞給內部查詢,然後執行內部查詢,根據內部查詢的執行結果判斷當前數據是否滿足外部查詢的where條件,若滿足則當前數據是符合要求的記錄
- 外部查詢依次掃描每條記錄,重複執行上述過程
https://blog.csdn.net/qiushisoftware/article/details/80874463
$count
- 返回輸入文檔的數量,並傳遞給下一階段
- 不改變文檔內容
格式
{ $count: <string> }
- string
- 輸出字段的名稱,值爲輸入文檔的數量
- 非空字符串,不能以
$
開頭,不能包含點.
字符
示例
-
$count 行爲等價於 $group + $project
db.collection.aggregate( [ { $group: { _id: null, myCount: { $sum: 1 } } }, { $project: { _id: 0 } } ] ) db.collection.aggregate([ { $count:"myCount" } ])
$unwind
- 解析輸入文檔中的數組字段,將每個元素作爲字段值輸出單獨的文檔
格式
{ $unwind: <field path> }
{
$unwind:
{
path: <field path>,
includeArrayIndex: <string>,
preserveNullAndEmptyArrays: <boolean>
}
}
-
path
- 類型:字符串
- 描述:指定要解析的數組字段
-
includeArrayIndex
- 類型:字符串
- 描述:新字段的名稱,用於保存元素在原數組中的索引,不能以
$
開頭
-
preserveNullAndEmptyArrays
preserve 保留
-
類型:布爾
-
描述:如果文檔不包含 path 指定的字段,或字段值爲null,或字段值爲空數組
[ ]
-
true
$unwind 原樣輸出該文檔
-
false【默認】
$unwind 不輸出該文檔
-
-
示例
-
preserveNullAndEmptyArrays 默認false
db.inventory2.insertMany([ { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] }, { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] }, { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" }, { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") }, { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null } ])
展開
db.inventory2.aggregate( [ { $unwind: "$sizes" } ] ) db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )
兩種語法效果一樣
結果
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" } { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
- size 字段丟失、爲null、爲空數組的文檔,默認不輸出
-
記錄索引,輸出文檔
db.inventory2.aggregate( [ { $unwind: { path: "$sizes", includeArrayIndex: "arrayIndex", preserveNullAndEmptyArrays: true } }])
結果
{ "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" } { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" } { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") } { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" } { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") } { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }
-
解析嵌套數組
- 先解析外層數組,再解析內層數組,path路徑需要通過點表示法引用內層數組
{ _id: "1", "items" : [ { "name" : "pens", "tags" : [ "writing", "office", "school", "stationary" ], "price" : NumberDecimal("12.00"), "quantity" : NumberInt("5") }, { "name" : "envelopes", "tags" : [ "stationary", "office" ], "price" : NumberDecimal("1.95"), "quantity" : NumberInt("8") } ] }
db.sales.aggregate([ // First Stage { $unwind: "$items" }, // Second Stage { $unwind: "$items.tags" } ])
$out
-
將聚合操作的結果寫入指定的集合
-
$out 必須是管道的最後一個階段
-
不能寫入固定集合中
-
如果指定的集合不存在,則在完成聚合操作後,會創建該集合
在聚合操作完成之前,該集合是不可見的。如果聚合操作失敗,則不會創建
-
如果指定的集合已經存在,則在完成聚合操作後,會用聚合操作結果覆蓋原有數據
-
$out 操作符不會改變原集合上建立的索引,如果聚合操作的結果文檔違反任一唯一索引(包括原集合
_id
字段上建立的索引),則寫入失敗
格式
{ $out: "<output-collection>" }
- output-collection
- 輸出集合的名稱
示例
db.books.insert([
{ "_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 }
])
聚合
db.books.aggregate( [
{ $group : { _id : "$author", books: { $push: "$title" } } },
{ $out : "authors" }
] )
結果在當前數據庫中新增 authors 集合
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] }
{ "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }
$merge
$addFields
- 向輸入文檔中添加新字段,返回包含所有原有字段和新添加字段的文檔
- 相當於 $project 顯式輸出所有原有字段並添加新字段
- mongodb 4.2 新增 $set 階段是 $addFields 的別名
格式
{ $addFields: { <newField>: <expression>, ... } }
- newField
- 新增字段名
- 如果已存在該字段,則覆蓋原有字段
Operators
## 僅 Group ##
$push
- 利用 $group 分組後,對同組內的文檔,以數組的形式,返回指定字段的值
- 只能應用於 $group 階段
格式
{ $push: <expression> }
$addToSet
- 利用 $group 分組後,對同組內的文檔,以數組的形式,返回指定字段 不重複的值
- 只能應用於 $group 階段
格式
{ $addToSet: <expression> }
- 如果 expression 解析爲某個字段的值
"$<field>"
,則以數組的形式返回該字段不重複的值
$sum
-
利用 $group 分組後,對同組內的文檔,對指定字段的數值進行求和
{$sum:1}
-
表示計算文檔數量總和,管道中的每個文檔代表數值1
-
在 $group 階段,分組過程中,計算每組文檔的數量
-
-
3.2 版本及之前,僅用於 $group 階段
格式
$group 階段
{ $sum: <expression> }
其他階段
{ $sum: <expression> }
{ $sum: [ <expression1>, <expression2> ... ] }
-
非數字或不存在字段
Example Field Values Results { $sum : <field> }
Numeric
Sum of Values
{ $sum : <field> }
Numeric and Non-Numeric
Sum of Numeric Values
{ $sum : <field> }
Non-Numeric or Non-Existent
0
- 在包含數字和非數字的字段上使用,則忽略非數字字段,返回數字值之和
- 如果字段不存在,則返回0
- 如果所有操作數都是非數字,則返回0
-
數組字段
- $group 階段,如果表達式解析爲數組,則視爲非數字值
- 其他階段
- 單個表達式作爲操作數,解析爲數組時,$sum 遍歷數組,對數字值求和並返回
- 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $sum 不會遍歷數組,視爲非數字值
$avg
-
利用 $group 分組後,對同組內的文檔,對指定字段的數值求平均值
-
3.2 版本及之前,僅用於 $group 階段
格式
$group 階段
{ $avg: <expression> }
其他階段
{ $avg: <expression> }
{ $avg: [ <expression1>, <expression2> ... ] }
- 忽略非數字值和丟失的值
- 如果操作數都是非數字值,則返回 null
- 數組字段
- $group 階段,如果表達式解析爲數組,則視爲非數字值
- 其他階段
- 單個表達式作爲操作數,解析爲數組時,$sum 遍歷數組,對數字值求和並返回
- 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $sum 不會遍歷數組,視爲非數字值
$first
-
利用 $group 分組後,對同組內的文檔,返回 指定字段 的第一個值
-
一般在文檔排序後使用纔有意義
-
當在 $group 階段中使用$first時,$group 階段應該在 $sort 階段之後,以使輸入文檔按照已定義的順序
-
儘管 $sort 階段將有序的文檔作爲輸入傳遞到 $group 階段,但 $group 不能保證在其自己的輸出中維護這種排序順序。
-
-
只能應用於 $group 階段
格式
{ $first: <expression> }
$last
-
利用 $group 分組後,對同組內的文檔,返回 指定字段 的最後一個值
-
一般在文檔排序後使用纔有意義
當在 $group 階段中使用$first時,$group 階段應該在 $sort 階段之後,以使輸入文檔按照已定義的順序
-
只能應用於 $group 階段
格式
{ $last: <expression> }
$max
- 利用 $group 分組後,對同組內的文檔,返回 指定字段 的最大值
- 3.2 版本及之前,僅用於 $group 階段
格式
$group 階段
{ $max: <expression> }
其他階段
{ $max: <expression> }
{ $max: [ <expression1>, <expression2> ... ] }
- 忽略 null 值和丟失字段,僅考慮字段的非空值和非缺失值
- 如果所有字段的值都爲 null 或缺失,則返回 null
- 數組字段
- 在$group階段,如果表達式解析爲一個數組,則$max不會遍歷該數組並將該數組作爲一個整體進行比較。
- 其他階段
- 單個表達式作爲操作數,解析爲數組時,$max 遍歷數組,對數字值進行操作並返回元素最大值
- 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $max 不會遍歷數組,視爲非數字值
$min
- 利用 $group 分組後,對同組內的文檔,返回 指定字段 的最小值
- 3.2 版本及之前,僅用於 $group 階段
格式
$group 階段
{ $min: <expression> }
其他階段
{ $min: <expression> }
{ $min: [ <expression1>, <expression2> ... ] }
- 忽略 null 值和丟失字段,僅考慮字段的非空值和非缺失值
- 如果所有字段的值都爲 null 或缺失,則返回 null
- 數組字段
- 在$group階段,如果表達式解析爲一個數組,則 $min 不會遍歷該數組並將該數組作爲一個整體進行比較。
- 其他階段
- 單個表達式作爲操作數,解析爲數組時,$min 遍歷數組,對數字值進行操作並返回元素最小值
- 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $min 不會遍歷數組,視爲非數字值
## 條件判斷 ##
$switch
- 對指定字段進行一系列的條件判斷,當條件爲true時,執行對應的表達式並跳出控制流
- 各個case語句不需要互相排斥,$switch 執行第一個計算結果爲true的分支
- 以下情況,$switch報錯
- branches 字段缺失或不是一個數組
- 條件語句不包含 case 字段
- 條件語句不包含 then 字段
- 條件語句包含 case、then 之外的字段
- 未指定 default 表達式且沒有條件語句計算結果爲true
格式
$switch: {
branches: [
{ case: <expression>, then: <expression> },
{ case: <expression>, then: <expression> },
...
],
default: <expression>
}
-
branches
-
類型:文檔數組
-
描述:
-
控制分支,每個分支都必須具有 case 和 then 字段
-
case
值爲可以解析爲布爾值的任何有效的 表達式,如果不是則強制轉換爲布爾值
-
then
值爲任何有效的表達式
-
-
必須至少包含一個條件分支
-
-
-
default【可選】
- 沒有條件分支結果爲true時,執行默認表達式
- 如果沒有指定,且沒有條件分支爲true,則 $switch 報錯
示例
文檔
db.grades.insert([
{ "_id" : 1, "name" : "Susan Wilkes", "scores" : [ 87, 86, 78 ] },
{ "_id" : 2, "name" : "Bob Hanna", "scores" : [ 71, 64, 81 ] },
{ "_id" : 3, "name" : "James Torrelio", "scores" : [ 91, 84, 97 ] }
])
聚合
db.grades.aggregate( [
{
$project:
{
"name" : 1,
"summary" :
{
$switch:
{
branches: [
{
case: { $gte : [ { $avg : "$scores" }, 90 ] },
then: "Doing great!"
},
{
case: { $and : [ { $gte : [ { $avg : "$scores" }, 80 ] },
{ $lt : [ { $avg : "$scores" }, 90 ] } ] },
then: "Doing pretty well."
},
{
case: { $lt : [ { $avg : "$scores" }, 80 ] },
then: "Needs improvement."
}
],
default: "No scores found."
}
}
}
}
] )
-
結果
{ "_id" : 1, "name" : "Susan Wilkes", "summary" : "Doing pretty well." } { "_id" : 2, "name" : "Bob Hanna", "summary" : "Needs improvement." } { "_id" : 3, "name" : "James Torrelio", "summary" : "Doing great!" }
## 數組 ##
$size
- 計算並返回數組的長度
格式
{ $size: <expression> }
-
expression
可以解析爲數組的任何有效的表達式
## 字符串 ##
$substr
3.4 版本中被廢棄,現在是 substrBytes 的別名
- 返回字符串中的子字符串,從指定索引位置開始,包含指定數目的字符。索引從0開始
格式
{ $substr: [ <string>, <start>, <length> ] }
- 值爲 數組
- string 一般是字段路徑
$<字段名>
- 如果 start 是負數,則返回空字符串
- 如果 length 是負數,則返回從指定索引位置開始到末尾的子字符串
$indexOfBytes
-
查詢並返回子字符串在字段中第一次出現位置的索引,如果沒有找到則返回 -1
UTF-8 字節索引
格式
{ $indexOfBytes: [ <string expression>, <substring expression>, <start>, <end> ] }
- string expression
- 可以解析爲字符串的任何有效的表達式
- 如果表達式解析爲 null 或引用丟失的字段,則 $indexOfBytes 返回 null
- 非以上情況返回一個錯誤
- substring expression
- 可以解析爲字符串的任何有效的表達式
- start
- 指定搜索的起始索引位置
- 非負整數,從0開始
- end
- 指定搜索的結束索引位置
- 非負整數
- 指定 end,則必須同時指定 start
示例
db.inventory.insert([
{ "_id" : 1, "item" : "foo" },
{ "_id" : 2, "item" : "fóofoo" },
{ "_id" : 3, "item" : "the foo bar" },
{ "_id" : 4, "item" : "hello world fóo" },
{ "_id" : 5, "item" : null },
{ "_id" : 6, "amount" : 3 }
])
聚合
db.inventory.aggregate(
[
{
$project:
{
byteLocation: { $indexOfBytes: [ "$item", "foo" ] },
}
}
]
)
-
結果
{ "_id" : 1, "byteLocation" : "0" } { "_id" : 2, "byteLocation" : "4" } { "_id" : 3, "byteLocation" : "4" } { "_id" : 4, "byteLocation" : "-1" } { "_id" : 5, "byteLocation" : null } { "_id" : 6, "byteLocation" : null }
-
注意 fóofoo 索引是4
é
is encoded using two bytes. -
每個文檔都有一個返回結果
-
$strLenBytes
- 返回指定字符串 UTF-8 編碼的字節數(字符串長度)
- 英文字母,使用 1個字節編碼(空串 0字節)
- 中文、日文、韓文,使用 3字節編碼
- 帶有變音符號的字符以及英語字母表之外的拉丁字符,使用2個字節編碼
格式
{ $strLenBytes: <string expression> }
- string expression
- 可以解析爲字符串的任何有效的表達式
- 如果表達式解析爲 null 或引用丟失的字段,則 $strLenBytes 返回一個錯誤
示例
文檔
{ "_id" : 1, "name" : "apple" }
{ "_id" : 2, "name" : "banana" }
{ "_id" : 3, "name" : "éclair" }
{ "_id" : 4, "name" : "hamburger" }
{ "_id" : 5, "name" : "jalapeño" }
{ "_id" : 6, "name" : "pizza" }
{ "_id" : 7, "name" : "tacos" }
{ "_id" : 8, "name" : "壽司" }
聚合
db.food.aggregate(
[
{
$project: {
"name": 1,
"length": { $strLenBytes: "$name" }
}
}
]
)
-
結果
{ "_id" : 1, "name" : "apple", "length" : 5 } { "_id" : 2, "name" : "banana", "length" : 6 } { "_id" : 3, "name" : "éclair", "length" : 7 } { "_id" : 4, "name" : "hamburger", "length" : 9 } { "_id" : 5, "name" : "jalapeño", "length" : 9 } { "_id" : 6, "name" : "pizza", "length" : 5 } { "_id" : 7, "name" : "tacos", "length" : 5 } { "_id" : 8, "name" : "壽司", "length" : 6 }
$strcasecmp
-
對兩個字符串執行不區分大小寫的比較
-
僅適用於 ASCII 字符編碼
格式
{ $strcasecmp: [ <expression1>, <expression2> ] }
- expression
- 可以解析爲字符串的任何有效的表達式
- 返回結果
- 1 :第一個字符串 大於 第二個字符串
- 0 :兩個字符串相等
- -1:第一個字符串 小於 第二個字符串
$toLower
- 將字符串轉換爲小寫字母並返回
- 僅適用於 ASCII 字符編碼
格式
{ $toLower: <expression> }
- expression
- 可以解析爲字符串的任何有效的表達式
- 如果表達式解析爲null,則返回空串
示例
db.inventory.aggregate(
[
{
$project:
{
item: { $toLower: "$item" },
description: { $toLower: "$description" }
}
}
]
)
$toUpper
-
將字符串轉換爲大寫字母並返回
-
僅適用於 ASCII 字符編碼
格式
{ $toUpper: <expression> }
- expression
- 可以解析爲字符串的任何有效的表達式
- 如果表達式解析爲null,則返回空串
$concat
- 合併字符串並返回
格式
{ $concat: [ <expression1>, <expression2>, ... ] }
- expression
- 可以解析爲字符串的任何有效的表達式
- 如果表達式解析爲 null 或引用丟失的字段,則返回 null
$split
- 根據指定的分隔符將字符串劃分爲子字符串數組
- 如果在字符串中沒有找到分隔符,則將原字符串作爲數組的唯一元素返回
- 返回的字符串數組中會忽略分隔符
格式
{ $split: [ <string expression>, <delimiter> ] }
- expression
- 類型:字符串
- 描述:被分隔的字符串
- 可以解析爲字符串的任何有效的表達式
- delimiter
- 類型:字符串
- 描述:指定分隔符
- 可以解析爲字符串的任何有效的表達式
Example | Results |
---|---|
{ $split: [ "June-15-2013", "-" ] } |
[ "June", "15", "2013" ] |
{ $split: [ "banana split", "a" ] } |
[ "b", "n", "n", " split" ] |
{ $split: [ "Hello World", " " ] } |
[ "Hello", "World" ] |
{ $split: [ "astronomical", "astro" ] } |
[ "", "nomical" ] |
{ $split: [ "pea green boat", "owl" ] } |
[ "pea green boat" ] |
{ $split: [ "headphone jack", 7 ] } |
Errors with message: ... |
{ $split: [ "headphone jack", /jack/ ] } |
Errors with message: ... |
"$split requires an expression that evaluates to a string as a second argument, found: regex"
## 比較運算 ##
$gt
- 比較兩個值並返回一個布爾值
格式
{ $gt: [ <expression1>, <expression2> ] }
- 返回值
- true:第一個表達式大於第二個表達式
- false:第一個表達式小於等於第二個表達式
- 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型
$gte
- 比較兩個值並返回一個布爾值
格式
{ $gte: [ <expression1>, <expression2> ] }
- 返回值
- true:第一個表達式大於等於第二個表達式
- false:第一個表達式小於第二個表達式
- 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型
$lt
- 比較兩個值並返回一個布爾值
格式
{ $lt: [ <expression1>, <expression2> ] }
- 返回值
- true:第一個表達式小於第二個表達式
- false:第一個表達式大於等於第二個表達式
- 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型
$lte
- 比較兩個值並返回一個布爾值
格式
{ $lte: [ <expression1>, <expression2> ] }
- 返回值
- true:第一個表達式小於等於第二個表達式
- false:第一個表達式大於第二個表達式
- 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型
## 算術運算 ##
$add
- 對數值類型或日期類型的字段進行加法運算
- 如果其中一個參數是日期類型,則將其他參數當作毫秒數相加並返回日期
格式
{ $add: [ <expression1>, <expression2>, ... ] }
- expression
- 可以解析爲數值或日期的任何有效的表達式
$subtract
- 對數值類型或日期類型的字段進行減法運算
- 兩個參數都是日期,則轉換爲毫秒數,返回差值
- 一個參數是日期,則將其他參數當作毫秒數相減並返回日期
格式
{ $subtract: [ <expression1>, <expression2> ] }
- expression
- 可以解析爲數值或日期的任何有效的表達式
- 第一個參數減去第二個參數
- 要從日期中減去一個數值,則日期必須是第一個參數
示例
文檔
db.sales.insertMany([
{ "_id" : 1, "item" : "abc", "price" : 10, "fee" : 2, "discount" : 5, "date" : ISODate("2014-03-01T08:00:00Z") },
{ "_id" : 2, "item" : "jkl", "price" : 20, "fee" : 1, "discount" : 2, "date" : ISODate("2014-03-01T09:00:00Z") }
])
聚合
db.sales.aggregate( [ { $project: { item: 1, dateDifference: { $subtract: [ "$date", 5 * 60 * 1000 ] } } } ] )
-
結果
{ "_id" : 1, "item" : "abc", "dateDifference" : ISODate("2014-03-01T07:55:00Z") } { "_id" : 2, "item" : "jkl", "dateDifference" : ISODate("2014-03-01T08:55:00Z") }
$multiply
- 將數字相乘並返回結果
- 對數值類型的字段進行乘法運算
格式
{ $multiply: [ <expression1>, <expression2>, ... ] }
- expression
- 可以解析爲數值的任何有效的表達式
$divide
- 對數值類型的字段進行除法運算
格式
{ $divide: [ <expression1>, <expression2> ] }
-
expression
- 可以解析爲數值的任何有效的表達式
- 第一個參數除以第二個參數
-
結果可以保留很多位小數
{ "_id" : 1, "item" : "abc", "dateDiff" : NumberDecimal("3.333333333333333333333333333333333") }
$mod
- 對數值類型的字段進行取模運算(取餘數)
格式
{ $mod: [ <expression1>, <expression2> ] }
- expression
- 可以解析爲數值的任何有效的表達式
- 第一個參數除以第二個參數
## 時間日期 ##
$year
- 返回日期的年份部分
- 如果值爲字符串,則報錯
格式
{ $year: <dateExpression> }
-
dateExpression【該部分操作符的格式具有相同的 dateExpression 規範】
-
可以解析爲 日期、時間戳、ObjectID 的有效表達式
-
或具有如下格式的文檔
{ date: <dateExpression>, timezone: <tzExpression> }
-
date
- $year 操作符獲取的日期
- dateExpression
- 可以解析爲 日期、時間戳、ObjectID 的有效表達式
-
timezone【可選】
- 指定時區,默認以 UTC 格式顯示
-
-
示例
Example | Result |
---|---|
{ $year: new Date("2016-01-01") } |
2016 |
{ $year: { date: new Date("Jan 7, 2003") } } |
2003 |
{ $year: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } |
2011 |
{ $year: ISODate("1998-11-07T00:00:00Z") } |
1998 |
{ $year: { date: ISODate("1998-11-07T00:00:00Z"), timezone: "-0400" } } |
1998 |
{ $year: "March 28, 1976" } |
error |
{ $year: Date("2016-01-01") } |
error |
{ $year: "2009-04-09" } |
error |
示例
{
"_id" : 1,
"date" : ISODate("2014-01-01T08:15:39.736Z")
}
聚合
db.sales.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" : 1, "year" : 2014, "month" : 1, "day" : 1, "hour" : 8, "minutes" : 15, "seconds" : 39, "milliseconds" : 736, "dayOfYear" : 1, "dayOfWeek" : 4, "week" : 0 }
$month
- 返回日期的月份部分(值爲1~12之間)
格式
{ $month: <dateExpression> }
示例
Example | Result |
---|---|
{ $month: new Date("2016-01-01") } |
1 |
{ $month: { date: new Date("Nov 7, 2003") } } |
11 |
{ $month: ISODate("2000-01-01T00:00:00Z") } |
1 |
{ $month: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } |
8 |
{ $month: { date: ISODate("2000-01-01T00:00:00Z"), timezone: "-0500" } } |
12 |
{ $month: "March 28, 1976" } |
error |
{ $month: { date: Date("2016-01-01"), timezone: "-0500" } } |
error |
{ $month: "2009-04-09" } |
error |
$week
- 返回日期處於一年中的第幾周(值爲 0~53之間)
- 星期從星期日開始,第一週從一年中的第一個星期日開始,之前的日子在第0周
格式
{ $week: <dateExpression> }
示例
Example | Result |
---|---|
{ $week: new Date("Jan 1, 2016") } |
0 |
{ $week: { date: new Date("2016-01-04") } } |
1 |
{ $week: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } |
33 |
{ $week: ISODate("1998-11-01T00:00:00Z") } |
44 |
{ $week: { date: ISODate("1998-11-01T00:00:00Z"), timezone: "-0500" } } |
43 |
{ $week: "March 28, 1976" } |
error |
{ $week: Date("2016-01-01") } |
error |
{ $week: "2009-04-09" } |
error |
NOTE
$hour
cannot take a string as an argument.
$hour
- 返回日期的小時部分(值爲 0~23 之間)
格式
{ $hour: <dateExpression> }
$minute
- 返回日期的分鐘部分(值爲0~59之間)
格式
{ $minute: <dateExpression> }
$second
- 返回日期的秒數部分(值爲0~59之間,閏秒60)
格式
{ $second: <dateExpression> }
$millisecond
- 返回日期的毫秒部分(值爲0~999之間)
格式
{ $millisecond: <dateExpression> }
$dayOfYear
- 返回日期處於一年中的第幾天(值爲1~366之間)
格式
{ $dayOfYear: <dateExpression> }
$dayOfMonth
- 返回日期處於這月中的第幾天(值爲1~31之間)
格式
{ $dayOfMonth: <dateExpression> }
$dayOfWeek
- 返回日期處於這週中的第幾天(值爲1~7)
格式
{ $dayOfWeek: <dateExpression> }
map-reduce
-
是一種數據處理範式(典範模式),將大量數據處理壓縮爲有用的聚合結果
-
處理過程
-
第一階段——映射(map)
將輸入文檔中 指定字段 的值保存到一個數組中(value),映射到 分組字段的值(key),以鍵值對(key-value)的形式輸出至 reduce 階段
-
第二階段——歸約(reduce)
根據需求對 相同鍵 的值 做運算,將運算結果作爲 文檔返回 或 寫入集合
-
-
map-reduce 使用定製的 JavaScript函數——map 函數,將值(指定字段)映射到一個鍵(分組字段),如果一個鍵有多個值,reduce 函數會將其處理壓縮爲一個對象
-
支持在分片集合上執行 map-reduce 操作,從 mongodb 4.2 開始
- 若要輸出到分片集合,需提前創建
- 不建議替換已存在的分片集合
-
視圖不支持 map-reduce 操作
-
vs 聚合管道
-
對大多數聚合操作,聚合管道提供更好的性能和更一致的接口,但是 map-reduce 操作提供了一些目前在聚合管道中無法提供的靈活性
-
map-reduce 的結果文檔中只包含新增字段,聚合管道可以通過 $project 控制結果文檔中包含原有字段
reduce 函數返回結果文檔
{ "_id": "<key>", "value": <執行結果> }
可以在 finalize 函數中添加新字段
-
格式
db.collection.mapReduce(
<map>, // 類似 $group
<reduce>,
{
out: <collection>, // 類似 $out
query: <document>, // 類似 $match
sort: <document>, // 類似 $sort
limit: <number>, // 類似 $limit
finalize: <function>, // 類似 $project
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>
}
)
-
map
-
類型:function
-
描述:
-
JavaScript函數,將輸入文檔中 指定字段的值 保存到一個數組中(value),映射到 分組字段的值(key),輸出至 reduce 函數中
-
作用於每個輸入文檔,以分組字段的值爲鍵(唯一),以指定字段的值爲value,將值作爲數組元素映射到鍵。執行完所有輸入文檔後,將組成的鍵值對輸出至reduce函數中
相同的鍵只產生一個鍵值對
-
-
格式
function() { ... emit(key, value); }
-
在 map 函數中,通過
this
引用當前文檔 -
map 函數不能訪問數據庫
-
map 函數不能對函數外產生影響
-
可以訪問在 scope 參數中定義的全局變量
-
一個 emit 只能容納 mongodb 最大 BSON 文檔大小的一半(8MB)
-
map 函數可以多次調用
emit( key, value)
函數,創建鍵值對-
如果 key 已經存在,則將 value 加入key對應的 value數組中
-
如果 key 不存在,則創建新的 key-value 鍵值對
根據輸入文檔字段的值,調用 0 次或1次 emit 函數
function() { if (this.status == 'A') emit(this.cust_id, 1); }
根據輸入文檔數組字段的元素個數,多次調用 emit 函數
function() { this.items.forEach(function(item){ emit(item.sku, 1); }); }
-
-
-
-
reduce
-
類型:function
-
描述:JavaScript函數,將與鍵關聯的值數組處理壓縮爲一個對象
-
格式
function(key, values) { ... return result; }
-
reduce 函數不能訪問數據庫
-
reduce 函數不能對函數外產生影響
-
values 類型爲數組,元素爲映射到鍵的值對象
-
可以訪問在 scope 參數中定義的全局變量
-
輸入 reduce 函數的文檔,其大小不能大於mongodb最大BSON文檔大小的一半(8MB)
但是reduce函數處理過程中可以超過
-
可以多次調用 reduce 函數,對於指定的鍵,前一個reduce函數的輸出將作爲下一個reduce函數的輸入
-
對同一個鍵多次調用reduce函數,或需要執行 finalize 函數,此時
-
reduce 返回值的類型必須與map函數輸出值的類型一致,即鍵值對形式
-
reduce 函數必須是可結合的,以下聲明必須爲true
reduce(key, [ C, reduce(key, [ A, B ]) ] ) == reduce( key, [ C, A, B ] )
-
reduce 函數必須是冪等的,以下聲明必須爲true
reduce( key, [ reduce(key, valuesArray) ] ) == reduce( key, valuesArray )
-
reduce 函數應該是可交換的,values 數組中的元素順序不應該影響輸出結果,以下聲明爲true
reduce( key, [ A, B ] ) == reduce( key, [ B, A ] )
-
-
-
-
options
-
out
- 類型:字符串或Document
-
描述:指定 map-reduce 操作結果的輸出形式,可以輸出到指定集合中,也可以內聯輸出
-
格式
輸出到指定集合
out: <collectionName>
- 如果集合已經存在,則會 替換 現有文檔
指定輸出模式
out: { <action>: <collectionName> [, db: <dbName>] [, sharded: <boolean> ] [, nonAtomic: <boolean> ] }
-
action
- replace
- 如果輸出集合已經存在,則用結果文檔 替換 現有文檔
- merge
- 如果輸出集合已經存在,則將結果文檔與現有文檔合併,如果現有文檔與結果文檔具有相同的鍵,則覆蓋現有文檔
- reduce
- 如果輸出集合已經存在,則將結果文檔與現有文檔合併。如果現有文檔與結果文檔具有相同的鍵,則對結果文檔和現有文檔應用reduce函數,並用結果覆蓋現有文檔
- replace
-
db
-
指定 map-reduce 輸出集合所在的數據庫
-
默認爲與輸入集合相同的數據庫
-
-
sharded
- 4.2 被廢棄
-
nonAtomic
- 將輸出操作指定爲非原子操作,只適用於 merge 和 reduce 輸出模式,需要幾分鐘的時間來執行
- 默認情況下,nonAtomic 爲 false, map-reduce操作在後處理期間鎖定數據庫
- 如果 nonAtomic 爲 true,後處理步驟將防止 MongoDB 鎖定數據庫,在此期間,其他客戶端將能夠讀取輸出集合的中間狀態
內聯輸出
out: { inline: 1 }
-
-
query
- 類型:Document
- 描述
- 使用查詢操作符指定查詢條件,對輸入文檔進行篩選
- 在 map 函數之前執行
-
sort
-
類型:Document
-
描述
-
對輸入文檔進行排序,有利於優化 map-reduce 操作
例如將排序鍵指定爲與 emit 相同的鍵
-
指定排序的鍵必須是集合中已經建立索引的鍵
-
在 map 函數之前執行
-
-
-
limit
- 類型:number
- 描述
- 限制輸入文檔的數量
- 在map函數之前執行
-
finalize
-
類型:function
-
描述
- 對 reduce 輸出結果進行修改
- 在 reduce 函數之後執行
-
格式
function(key, reducedValue) { ... return modifiedObject; }
- finalize 函數不能訪問數據庫
- finalize 函數不能對函數外產生影響
- 可以訪問在 scope 參數中定義的全局變量
-
-
scope
- 類型:Document
- 描述
- 定義可在 map、reduce、finalize 函數中訪問的全局變量
-
jsMode
- 類型:布爾
- 描述:是否在 map-reduce 過程中將數據轉換成 BSON 格式
- false【默認】
-
verbose
- 類型:布爾
- 描述:是否在結果信息中包含時間信息
- false【默認】
-
collation
- 類型:Document
- 描述:指定排序規則
-
bypassDocumentValidation
- 類型:布爾
- 描述:在 map-reduce 過程中是否繞過數據驗證,允許插入不滿足數據驗證的文檔
JS Function
map-reduce 操作和 $where 操作符表達式不能訪問某些全局函數或屬性,例如db
以下函數和屬性可以訪問
Available Properties | Available Functions | ||
---|---|---|---|
args | assert() | isNumber() | print() |
MaxKey | BinData() | isObject() | printjson() |
MinKey | DBPointer() | ISODate() | printjsononeline() |
DBRef() | isString() | sleep() | |
doassert() | Map() | Timestamp() | |
emit() | MD5() | tojson() | |
gc() | NumberInt() | tojsononeline() | |
HexData() | NumberLong() | tojsonObject() | |
hex_md5() | ObjectId() | UUID() | |
version() |
示例
普通文檔
計算每個消費者的總價
以 cust_id字段 分組,計算每組中 price字段的總和
文檔
db.orders.insertMany([
{ _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" },
{ _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" },
{ _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"},
{ _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
{ _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }
])
-
map-reduce 操作
-
定義 map 函數
- 將 price字段 映射到 cust_id 字段,輸出 cust_id-price 鍵值對
var mapFunction1 = function() { emit(this.cust_id, this.price); };
-
定義 reduce 函數
-
接收 map 函數返回的鍵值對列表,對每個鍵值對執行 reduce 函數
-
將鍵作爲
_id
字段的值,執行結果作爲value
字段的值,以文檔{ "_id": "<key>", "value": <執行結果> }
的形式返回
var reduceFunction1 = function(keyCustId, valuesPrices) { // 具有一些 JS 目前沒有的接口 return Array.sum(valuesPrices); };
-
-
對集合中的所有文檔執行 map-reduce 操作
- 將結果輸出到 map_reduce_example 集合中
db.orders.mapReduce( mapFunction1, reduceFunction1, { out: "map_reduce_example" } )
-
-
聚合操作
db.orders.aggregate([ { $group: { _id: "$cust_id", value: { $sum: "$price" } } }, { $out: "agg_alternative_1" } ])
嵌套文檔
計算2020-03-01之後,每個sku(單品)的訂單數,總銷量,以及每個訂單平均銷量
以 sku 字段分組,計算每組中 qty 字段的總和,以及每組中
_id
字段不同的文檔的數量,最後計算平均銷量
-
map-reduce 操作
-
定義map函數
- 將包含 count 和 qty 字段的文檔,映射到 sku 字段,輸出鍵值對 key-value
var mapFunction2 = function() { // 遍歷數組,對嵌套子文檔執行 emit 函數 // 並不是每個元素都創建一個鍵值對,key已經存在時,只會將 value 放入對應的 value數組 for (var idx = 0; idx < this.items.length; idx++) { var key = this.items[idx].sku; var value = { count: 1, qty: this.items[idx].qty }; emit(key, value); } };
-
定義 reduce 函數
- 接收 map 函數返回的鍵值對列表,對每個鍵值對執行 reduce 函數
- 統計每個"值"中 count 字段和 qty 字段的值,輸出鍵值對 key-reduceVal
var reduceFunction2 = function(keySKU, countObjVals) { reducedVal = { count: 0, qty: 0 }; for (var idx = 0; idx < countObjVals.length; idx++) { reducedVal.count += countObjVals[idx].count; reducedVal.qty += countObjVals[idx].qty; } return reducedVal; };
-
定義finalize 函數
- 接收 reduce 函數返回的鍵值對列表,對每個鍵值對執行 finalize 函數
- 在返回結果文檔中添加新字段 avg
var finalizeFunction2 = function (key, reducedVal) { reducedVal.avg = reducedVal.qty/reducedVal.count; return reducedVal; };
-
對集合中的所有文檔執行 map-reduce 操作
- 將結果文檔合併到已存在的數據庫中
db.orders.mapReduce( mapFunction2, reduceFunction2, { out: { merge: "map_reduce_example2" }, query: { ord_date: { $gte: new Date("2020-03-01") } }, finalize: finalizeFunction2 } );
-
-
聚合管道
db.orders.aggregate( [ { $match: { ord_date: { $gte: new Date("2020-03-01") } } }, { $unwind: "$items" }, { $group: { _id: "$items.sku", qty: { $sum: "$items.qty" }, orders_ids: { $addToSet: "$_id" } } }, { $project: { value: { count: { $size: "$orders_ids" }, qty: "$qty", avg: { $divide: [ "$qty", { $size: "$orders_ids" } ] } } } }, { $merge: { into: "agg_alternative_3", on: "_id", whenMatched: "replace", whenNotMatched: "insert" } } ] )
- $match 階段,相當於 map-reduce 中的 query 參數
- $unwind 階段,將嵌套數組拆分成單獨的文檔
- $group 階段,相當於 map-reduce 中的 map 和 reduce 函數
- $project 階段,相當於 map-reduce 中的 finalize 函數
- $merge 階段,相等於 map-reduce 中的 out 參數