MongoDB屬于 NoSql 中的基于分布式文件存儲的文檔型數(shù)據(jù)庫,是非關(guān)系數(shù)據(jù)庫當(dāng)中功能最豐富,最像關(guān)系數(shù)據(jù)庫的。它支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似 json 的 bson 格式,因此可以存儲比較復(fù)雜的數(shù)據(jù)類型。Mongo 最大的特點是它支持的查詢語言非常強大,其語法有點類似于面向?qū)ο蟮牟樵冋Z言,幾乎可以實現(xiàn)類似關(guān)系數(shù)據(jù)庫單表查詢的絕大部分功能,但是寫起來并不簡單。若能集算器 SPL 語言結(jié)合,處理起來就相對容易多了。
現(xiàn)在我們針對 MongoDB 在計算方面的問題進行討論分析,通過集算器 SPL 語言加以改進,方便用戶使用 MongoDB?,F(xiàn)從如下情況加以說明:
1. 單表內(nèi)嵌數(shù)組結(jié)構(gòu)的統(tǒng)計............................................... 1 1. 單表內(nèi)嵌數(shù)組結(jié)構(gòu)的統(tǒng)計對嵌套數(shù)組結(jié)構(gòu)中的數(shù)據(jù)統(tǒng)計處理。查詢考試科目的平均分及每個學(xué)生的總成績情況。測試數(shù)據(jù): 腳本: db.student.aggregate( [ {\$unwind : "\$scroe"}, {\$group: { "_id": {"lesson":"\$scroe.lesson"} , "qty":{"\$avg": "\$scroe.mark"} } } ] ) db.student.aggregate( [ {\$unwind : "\$scroe"}, {\$group: { "_id": {"name" :"\$name"} , "qty":{"\$sum" : "\$scroe.mark"} } } ] ) 由于各科分數(shù) scroe 是按課目、成績記錄的數(shù)組結(jié)構(gòu),統(tǒng)計前需要將它拆解,將每科成績與學(xué)生對應(yīng),然后再實現(xiàn)分組計算。這需要熟悉 unwind 與 group 組合的應(yīng)用。 SPL 腳本: 按課目統(tǒng)計的總分數(shù) 腳本說明: A1:連接 mongo 數(shù)據(jù)庫。 2. 單表內(nèi)嵌文檔求和對內(nèi)嵌文檔中的數(shù)據(jù)求和處理, 下面要統(tǒng)計每條記錄的 income,output 的數(shù)量和。測試數(shù)據(jù): Mongodb腳本: var fields = [ "income", "output"]; db.computer.aggregate([ { \$project:{ "values":{ \$filter:{ input:{ "\$objectToArray":"\$\$ROOT" }, cond:{ \$in:[ "\$\$this.k", fields ] } } } } }, { \$unwind:"\$values" }, { \$project:{ key:"\$values.k", values:{ "\$sum":{ "\$let":{ "vars":{ "item":{ "\$objectToArray":"\$values.v" } }, "in":"\$\$item.v" } } } } }, {\$sort: {"_id":-1}}, { "\$group": { "_id": "\$_id", ''income'':{"\$first": "\$values"}, "output":{"\$last": "\$values"} }}, ]); filter將income,output 部分信息存放到數(shù)組中,用 unwind 拆解成記錄,再累計各項值求和,按 _id 分組合并數(shù)據(jù)。 SPL 腳本: 腳本說明: A1:連接數(shù)據(jù)庫 獲取子記錄的字段值,然后求和,相對于 mongo 腳本簡化了不少。這個內(nèi)嵌文檔與內(nèi)嵌數(shù)組在組織結(jié)構(gòu)上有點類似,不小心容易混淆,注意與上例中的 scroe 數(shù)組結(jié)構(gòu)比較,寫出的腳本有所不同。 3. 分段分組結(jié)構(gòu)統(tǒng)計各段內(nèi)的記錄數(shù)量。下面按銷售量分段,統(tǒng)計各段內(nèi)的數(shù)據(jù)量,數(shù)據(jù)如下: 分段方法:0-3000;3000-5000;5000-7500;7500-10000;10000 以上。 期望結(jié)果: Mongo 腳本 var a_count=0; var b_count=0; var c_count=0; var d_count=0; var e_count=0; db.sales.find({ }).forEach( function(myDoc) { if (myDoc.SALES <3000) { a_count += 1; } else if (myDoc.SALES <5000) { b_count += 1; } else if (myDoc.SALES <7500) { c_count += 1; } else if (myDoc.SALES <10000) { d_count += 1; } else { e_count += 1; } } ); print("a_count="+a_count) print("b_count="+b_count) print("c_count="+c_count) print("d_count="+d_count) print("e_count="+e_count) 這個需求按條件分段分組,mongodb 沒有提供對應(yīng)的 api,實現(xiàn)起來有點繁瑣,上面的程序是其中實現(xiàn)的一個例子參考,當(dāng)然也可以寫成其它實現(xiàn)形式。下面看看集算器腳本的實現(xiàn)。 SPL 腳本: 腳本說明: A1:定義 SALES 分組區(qū)間。
A2:連接 mongodb 數(shù)據(jù)庫。 A3:獲取 sales 表中的數(shù)據(jù)。 A4:根據(jù) SALES 區(qū)間分組統(tǒng)計員工數(shù)。其中函數(shù) pseg()表示返回成員在序列中的區(qū)段序號,int() 表示轉(zhuǎn)換成整數(shù)。 A5:關(guān)閉數(shù)據(jù)庫連接。
pseg 的使用讓 SPL 腳本精簡了不少。 4. 同構(gòu)表合并具有相同結(jié)構(gòu)的多表數(shù)據(jù)合并。下面將兩個員工表數(shù)據(jù)合并。Emp1: Mongo 腳本: db.emp1.aggregate([ { "\$limit": 1}, { "\$facet": { "collection1": [ {"\$limit": 1}, { "\$lookup": { "from": "emp1", "pipeline": [{"\$match": {} }], "as": "collection1" }} ], "collection2": [ {"\$limit": 1}, { "\$lookup": { "from": "emp2", "pipeline": [{"\$match": {} }], "as": "collection2" }} ] }}, { "\$project": { "data": { "\$concatArrays": [ {"\$arrayElemAt": ["\$collection1.collection1", 0] }, {"\$arrayElemAt": ["\$collection2.collection2", 0] }, ] } }}, { "\$unwind": "\$data"}, { "\$replaceRoot": { "newRoot": "\$data"} } ]) 通過 facet 將兩表數(shù)據(jù)先存入各自的數(shù)組中,然后 concatArrays 將數(shù)組合并,unwind 拆解子記錄后,并將它呈現(xiàn)在最外層。SPL 腳本實現(xiàn)則沒有那么多“花樣”。 SPL 腳本: 腳本說明: A1:連接 mongodb 數(shù)據(jù)庫。 熟悉 sql 語句的 mongo 初學(xué)者面對數(shù)據(jù)合并的 mongo 腳本,估計首次遇到時有點“懵”,SPL 腳本就顯得自然易懂了。 5. 關(guān)聯(lián)嵌套結(jié)構(gòu)情況 1兩個關(guān)聯(lián)表,表 A 與表 B 中的內(nèi)嵌文檔信息關(guān)聯(lián), 且返回的信息在內(nèi)嵌文檔中。表 childsgroup 字段 childs 是嵌套數(shù)組結(jié)構(gòu),需要合并的信息 name 在其下。
history: 表History中的child_id與表childsgroup中的childs.id關(guān)聯(lián),希望得到下面結(jié)果: Mongo 腳本 db.history.aggregate([ {\$lookup: { from: "childsgroup", let: {child_id: "\$child_id"}, pipeline: [ {\$match: { \$expr: { \$in: [ "\$\$child_id", "\$childs.id"] } } }, {\$unwind: "\$childs"}, {\$match: { \$expr: { \$eq: [ "\$childs.id", "\$\$child_id"] } } }, {\$replaceRoot: { newRoot: "\$childs.info"} } ], as: "childInfo" }}, {"\$unwind": "\$childInfo"} ]) 這個腳本用了幾個函數(shù)lookup、pipeline、match、unwind、replaceRoot處理,一般 mongodb 用戶不容易寫出這樣復(fù)雜腳本;那我們再看看 spl 腳本的實現(xiàn): SPL 腳本: A1:連接 mongodb 數(shù)據(jù)庫。 A2:獲取 history 表 中的數(shù)據(jù)。 A3:獲取 childsgroup 表 中的數(shù)據(jù)。 A4:將 childsgroup 中的 childs 數(shù)據(jù)提取出來合并成序表。 A5:表 history 中的 child_id 與表 childs 中的 id 關(guān)聯(lián)查詢,追加 name 字段, 返回序表。 A6:關(guān)閉數(shù)據(jù)庫連接。
相對 mongodb 腳本寫法,SPL 腳本的難度降低了不少,省去了熟悉有關(guān) mongo 函數(shù)的用法,如何去組合處理數(shù)據(jù)等,節(jié)約了不少時間。 6. 關(guān)聯(lián)嵌套結(jié)構(gòu)情況 2兩個關(guān)聯(lián)表,表 A 與表 B 中的內(nèi)嵌文檔信息關(guān)聯(lián), 將信息合并到內(nèi)嵌文檔中。表 txtPost 字段 comment 是嵌套數(shù)組結(jié)構(gòu),需要把 comment_content 合并到其下。 Mongo 腳本 db.getCollection("txtPost").aggregate([ { "\$unwind": "\$comment"}, { "\$lookup": { "from": "txtComment", "localField": "comment.comment_no", "foreignField": "comment_no", "as": "comment.comment_content" }}, { "\$unwind": "\$comment.comment_content"}, { "\$addFields": { "comment.comment_content": "\$comment.comment_content.comment_content"}}, { "\$group": { "_id": "\$_id", ''post_no'':{"\$first": "\$post_no"}, "comment": {"\$push": "\$comment"} }},
]).pretty()表txtPost 按 comment 拆解成記錄,然后與表 txtComment 關(guān)聯(lián)查詢,將其結(jié)果放到數(shù)組中,再將數(shù)組拆解成記錄,將comment_content 值移到 comment 下,最后分組合并。 SPL 腳本: 腳本說明: A1:連接 mongodb 數(shù)據(jù)庫。
A2:獲取 txtPost 表 中的數(shù)據(jù)。 A3:獲取 txtComment 表 中的數(shù)據(jù)。 A4:將序表 A2 下的 comment 與 post_no 組合成序表,其中 post_no 改名為 pno。 A5:序表 A4 通過 comment_no 與序表 A3 關(guān)聯(lián),追加字段 comment_content,將其改名為 Content。 A6:按 pno 分組返回序表,~ 表示當(dāng)前記錄。 A7:關(guān)閉數(shù)據(jù)庫連接。 7. 關(guān)聯(lián)嵌套結(jié)構(gòu)情況 3兩個關(guān)聯(lián)表,表 A 與表 B 中的內(nèi)嵌文檔信息關(guān)聯(lián), 且返回的信息在記錄上。表 collection2 字段 product 是嵌套數(shù)組結(jié)構(gòu),返回的信息是 isCompleted 等字段。
測試數(shù)據(jù):
collection1: 期待結(jié)果 Mongo 腳本
lookup 兩表關(guān)聯(lián)查詢,首個 addFields獲取isCompleted數(shù)組的第一個記錄,后一個addFields 轉(zhuǎn)換成所需要的幾個字段信息
SPL
腳本: 腳本說明:
A1:連接 mongodb 數(shù)據(jù)庫。 A2:獲取 collection1 表 中的數(shù)據(jù)。 A3:獲取 collection2 表 中的數(shù)據(jù)。 A4:根據(jù)條件 order, lot 從序表 A2 中查詢記錄,然后追加序表 A3 中的字段 serialNo, batchNo,返回合并后的序表。 A5:關(guān)閉數(shù)據(jù)庫連接。 實現(xiàn)從數(shù)據(jù)記錄中的內(nèi)嵌結(jié)構(gòu)中篩選,將符合條件的數(shù)據(jù)合并成新序表。 8. 多字段分組統(tǒng)計統(tǒng)計分類項下的總數(shù)及各子項數(shù)。下面統(tǒng)計按 addr 分類 book 數(shù)及其下不同的 book 數(shù)。 Mongo 腳本 db.books.aggregate([ { "\$group": { "_id": { "addr": "\$addr", "book": "\$book" }, "bookCount": {"\$sum": 1} }}, { "\$group": { "_id": "\$_id.addr", "books": { "\$push": { "book": "\$_id.book", "count": "\$bookCount" }, }, "count": {"\$sum": "\$bookCount"} }}, { "\$sort": { "count": -1} }, { "\$project": { "books": {"\$slice": [ "\$books", 2] }, "count": 1 }} ]).pretty() 先按 addr,book 分組統(tǒng)計 book 數(shù),再按 addr 分組統(tǒng)計 book 數(shù),調(diào)整顯示順序 SPL腳本: 腳本說明: A1:連接 mongodb 數(shù)據(jù)庫。 9. 兩表關(guān)聯(lián)查詢從關(guān)聯(lián)表中選擇所需要的字段組合成新表。 Collection1: Mongo 腳本 db.c1.aggregate([ { "\$lookup": { "from": "c2", "localField": "user1", "foreignField": "user1", "as": "collection2_doc" }}, { "\$unwind": "\$collection2_doc"}, { "\$redact": { "\$cond": [ {"\$eq": [ "\$user2", "\$collection2_doc.user2"] }, "\$\$KEEP", "\$\$PRUNE" ] }}, { "\$project": { "user1": 1, "user2": 1, "income": "\$income", "output": "\$collection2_doc. output" }} ]).pretty() lookup 兩表進行關(guān)聯(lián)查詢,redact 對記錄根據(jù)條件進行遍歷處理,project 選擇要顯示的字段。 SPL腳本: 腳本說明: A1:連接 mongodb 數(shù)據(jù)庫。 通過 join 把兩個關(guān)聯(lián)表不同的字段合并成新表。 10. 多表關(guān)聯(lián)查詢多于兩個表的關(guān)聯(lián)查詢,結(jié)合成一張大表。 合并后的結(jié)果: { Mongo 腳本
由于 Mongodb 數(shù)據(jù)結(jié)構(gòu)原因,寫法也多樣化,展示也各不相同。
SPL
腳本: 此腳本與上面例子類似,只是多了一個關(guān)聯(lián)表,每次 join 就新增加字段,最后疊加構(gòu)成一張大表。.
SPL 腳本的簡潔性、統(tǒng)一性就非常明顯。 11. 指定數(shù)組查找從指定的數(shù)組中查找符合條件的記錄。所給的數(shù)組為:["Chemical", "Biology", "Math"]。 測試數(shù)據(jù): Mongodb 腳本 var field = ["Chemical", "Biology", "Math"] db.student.aggregate([ { "\$project": { "name":1, "lessons": { "\$filter": { "input": "\$lesson", "cond": { "\$in": [ "\$\$this", field ] } } }, }}, { "\$project": {"name":1,"lessons":1,"sizeOflesson": {"\$size": "\$lessons"} }}, { \$match: { "sizeOflesson":{ \$gt: 0}}} ]) 查詢選修課包含["Chemical", "Biology", "Math"]的同學(xué)。 SPL 腳本: 腳本說明: A1:定義查詢條件科目數(shù)組。 集算器對給定數(shù)組中查詢記錄的實現(xiàn)更簡明易懂。 12. 關(guān)聯(lián)表中的數(shù)組查找從關(guān)聯(lián)表記錄數(shù)據(jù)組中查找符合條件的記錄, 用給定的字段組合成新表。
測試數(shù)據(jù): Mongo 腳本 db.users.aggregate([ { "\$lookup": { "from" : "workouts", "localField" : "workouts", "foreignField" : "_id", "as" : "workoutDocumentsArray" }}, {\$project: { _id:0,workouts:0} } , {"\$unwind": "\$workoutDocumentsArray"},; {"\$replaceRoot": { "newRoot": { \$mergeObjects: [ "\$\$ROOT", "\$workoutDocumentsArray"] } } }, {$project: { workoutDocumentsArray: 0} } ]).pretty() 把關(guān)聯(lián)表 users,workouts 查詢結(jié)果放到數(shù)組中,再將數(shù)組拆解,提升子記錄的位置,去掉不需要的字段。 SPL 腳本: 腳本說明:
A1:連接 mongodb 數(shù)據(jù)庫。
A2:獲取 users 表中的數(shù)據(jù)。 A3:獲取 workouts 表中的數(shù)據(jù)。 A4:查詢序表 A3 的 _id 值存在于序表 A2 中 workouts 數(shù)組的記錄, 并追加 name 字段, 返回合并的序表。 A5:關(guān)閉數(shù)據(jù)庫連接。 由于需要獲取序列的交集不為空為條件,故將 _id 轉(zhuǎn)換成序列。 Mongo 存儲的數(shù)據(jù)結(jié)構(gòu)相對關(guān)聯(lián)數(shù)據(jù)庫更復(fù)雜、更靈活,其提供的查詢語言也非常強、能適應(yīng)不同的情況,需要了解函數(shù)也不少,函數(shù)之間的結(jié)合更是變化無窮,因此要掌握并熟悉應(yīng)用它并非易事。集算器的離散性、易用性恰好能彌補 Mongo 這方面的不足,它降低了 mongo 學(xué)習(xí)成本及使用 mongo 操作的復(fù)雜度、難度,讓 mongo 的功能得到更充分的展現(xiàn),同時也希望 mongo 越來越受到廣大愛好者的青睞。 |
|
來自: raqsoft > 《集算器&潤乾報表》