NoSQL | MongoDB入門實(shí)戰(zhàn)教程(6)

通過(guò)前面幾篇的學(xué)習(xí),作為后端開(kāi)發(fā)的我們基本可以應(yīng)付70%的開(kāi)發(fā)場(chǎng)景。接下來(lái),我們就來(lái)看點(diǎn)進(jìn)階一點(diǎn)的東西,首先是聚合查詢。
前面的學(xué)習(xí)我們都是針對(duì)單個(gè)Collection操作的,雖然在MongoDB中針對(duì)Collection的設(shè)計(jì)就已經(jīng)是無(wú)模式的,因此我們大部分場(chǎng)景都是針對(duì)單個(gè)Collection進(jìn)行操作。
但是,我們?cè)趯?shí)際應(yīng)用場(chǎng)景中還是會(huì)遇到想要SQL查詢中的 GROUP BY、LEFT OUTER JOIN、AS等操作。
好在,MongoDB提供了一套聚合框架(Aggregation Framework),它可以幫助我們?cè)谝粋€(gè)或多個(gè)Collection上,對(duì)Collection中的數(shù)據(jù)進(jìn)行一系列的計(jì)算,并將這些數(shù)據(jù)轉(zhuǎn)化為期望的格式。
整個(gè)聚合計(jì)算的過(guò)程也被稱之為管道(Pipeline),由多個(gè)步驟(Stage)組成,這一點(diǎn)和Jenkins Pipeline比較類似。其中,每個(gè)管道需要:
(1)接受一系列Document(原始數(shù)據(jù))
(2)每個(gè)步驟對(duì)這些Document進(jìn)行一系列的運(yùn)算
(3)結(jié)果Document輸出給下一個(gè)步驟
整個(gè)管道的過(guò)程如下圖所示:

聚合計(jì)算的基本格式如下所示:
pipeline = [$stage1, $stage2, ...$stageN];db.<CollectionName>.aggregate(pipeline,{ options });
示例數(shù)據(jù)數(shù)據(jù)庫(kù)
這里我們使用《MongoDB入門實(shí)戰(zhàn)教程(3)》中使用Mongo Tools進(jìn)行恢復(fù)的Mock數(shù)據(jù)庫(kù)中的orders集合來(lái)進(jìn)行應(yīng)用。

在orders集合中,約有100000條記錄。
每個(gè)order文檔的數(shù)據(jù)模型如下所示:


練習(xí)1:目前為止的訂單總銷量
假設(shè)我們需要針對(duì)orders集合進(jìn)行一個(gè)操作,計(jì)算到目前為止的所有訂單的總銷售額:
db.orders.aggregate([{ $group:{_id: null,total: { $sum: "$total" }}}]);
這里我們使用到了一個(gè)常見(jiàn)的步驟(Stage):$group,它和SQL中的GROUP BY等價(jià),用于對(duì)數(shù)據(jù)進(jìn)行分組。這里我們僅僅是做一個(gè)求和,不需要對(duì)誰(shuí)進(jìn)行分組。
然后,我們還用到了一個(gè)分組步驟中常用的運(yùn)算符:$sum,它和SQL中的SUM等價(jià),用于對(duì)指定列的數(shù)據(jù)進(jìn)行求和。這里我們需要對(duì)total字段進(jìn)行一個(gè)求和。
下圖是查詢結(jié)果:

練習(xí)2:某個(gè)日期區(qū)間的訂單金額匯總
假設(shè)我們需要查詢?cè)?019年第一季度已完成訂單的訂單總金額和訂單總數(shù)。
說(shuō)明:第一季度為1月1日~3月31日,訂單狀態(tài)為completed。
db.orders.aggregate([// 步驟1:匹配條件{ $match: { status: "completed", orderDate: {$gte: ISODate("2019-01-01"),$lt: ISODate("2019-04-01") } } },// 步驟二:聚合訂單總金額、總運(yùn)費(fèi)、總數(shù)量{ $group: {_id: null,total: { $sum: "$total" },shippingFee: { $sum: "$shippingFee" },count: { $sum: 1 } } },{ $project: {// 計(jì)算總金額grandTotal: { $add: ["$total", "$shippingFee"] },count: 1,_id: 0 } }])
可以看到,這是一個(gè)較為復(fù)雜的查詢,我們可以將其分為三步:
第一步,使用$match進(jìn)行匹配,這一點(diǎn)是做的SQL中的WHERE操作。
第二步,使用$group進(jìn)行分組,目的是為了使用SUM運(yùn)算符求和。
第三步,使用$project進(jìn)行投影,目的是選擇需要的或排除不需要的字段顯示。
下圖是查詢結(jié)果:

分頁(yè)查詢對(duì)比
在SQL中常使用SKIP 和 LIMIT 進(jìn)行分頁(yè)查詢,在MQL中也有等價(jià)操作:
-- SQLSELECTFIRST_NAME AS `名`,LAST_NAME AS `姓`FROM UsersWHERE GENDER = '男'SKIP 100LIMIT 20-- MQLdb.users.aggregate([{$match: {gender: "男"}},{$skip: 100},{$limit: 20},{$project: {'名': '$first_name','姓': '$last_name'}}]);
分組查詢對(duì)比
在SQL中常使用GROUP BY + HAVING 的分組高級(jí)查詢,在MQL中也有等價(jià)操作:
-- SQLSELECT DEPARTMENT,COUNT(NULL) AS EMP_QTYFROM UsersWHERE GENDER = '女'GROUP BY DEPARTMENT HAVINGCOUNT(*) < 10-- MQLdb.users.aggregate([{$match: {gender: '女'}},{$group: {_id: '$DEPARTMENT’,emp_qty: {$sum: 1}}},{$match: {emp_qty: {$lt: 10}}}]);
unwind
在MQL中有一個(gè)特有的步驟 unwind,它可以實(shí)現(xiàn)將文檔中的某一個(gè)數(shù)組類型字段拆分成多條,每條包含數(shù)組中的一個(gè)值。
> db.students.findOne(){name:'張三',score:[{subject:'語(yǔ)文',score:84},{subject:'數(shù)學(xué)',score:90},{subject:'外語(yǔ)',score:69}]}> db.students.aggregate([{$unwind: '$score'}]){name: '張三', score: {subject: '語(yǔ)文', score: 84}}{name: '張三', score: {subject: '數(shù)學(xué)', score: 90}}{name: '張三', score: {subject: '外語(yǔ)', score: 69}}
本文簡(jiǎn)單介紹了MongoDB的Aggregation Framework 以及 如何使用聚合框架進(jìn)行聚合查詢。
下一篇,我們會(huì)學(xué)習(xí)MongoDB的模式設(shè)計(jì)中的一些設(shè)計(jì)模式。
參考資料
唐建法,《MongoDB高手課》(極客時(shí)間)
郭遠(yuǎn)威,《MongoDB實(shí)戰(zhàn)指南》(圖書(shū))
