iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 15
0
AI & Data

與資料庫共舞系列 第 15

Day 15 — 一步一步寫出複雜的 MongoDB 指令

今天我要要開始寫一些再複雜一點的指令。首先我們來說說什麼是 Aggregation (聚合). Aggregation Pipeline 指的是把查詢拆成步驟,想水流一樣,層層處理。讓我們先看一個例子。

db.grades.aggregate(
	[{$match: {student_id: {$lte:5}}}, 
	 {$match: {class_id: {"$gte":400}}}]
)

Aggregate 這個函數帶的是一個陣列 (array) ,這個列表順序表示他針對這筆資料逐步做的處理。在這邊我們地一次將grades這裡的資料針對student_id 小於等於五的先過濾選出來,第二步再去對應出課程編號大於400的課。這個範例我刻意把一個很簡單的find 改寫成aggregate,但隨著你知道mongoDB 有哪一些好用的函式,就會知道aggreagte 強大的地方。在這裡我強烈推薦使用 Mongo Compass 的GUI 可以讓第一次寫aggregation的讀者快速上手並寫出非常複雜的指令。

https://ithelp.ithome.com.tw/upload/images/20200915/20129829HI6TA0meqs.png

在原本選擇在documents 的隔壁點選Aggregation. 這相當於在編輯每一段陣列以面的查詢。第一列市所有資料集合中的資料,下面我先做了一個 match ,這時候就會顯示出預覽符合這個條件的資料,然後在往下就是第二次過濾。完成後我們就可以點選中間綠色 Save 按鈕右邊的白色按鈕,看到整個查詢的指令。基本上就跟上面寫的一樣囉!

https://ithelp.ithome.com.tw/upload/images/20200915/201298296uiWV4KbpY.png

Unwind, Group, Project, AddToSet

先來介紹什麼事 unwind. 你可以把她想成是分裝。讓我們看下面這個虛擬的例子:

{ studnet_id: 1, friends: [2, 5, 7] } 

如果針對這個document 進行friends unwind,就會變成:

{ { studnet_id: 1, friends: 2 },
 { studnet_id: 1, friends: 5 },
 { studnet_id: 1, friends: 7 } } 

這個函式把相同的部分留下來,把指定的列表拆開成獨立的Document. 這有什麼好處呢? 讓我們看下面這個例子。回到原本的範例資料,看一下這個頗複雜的查詢指令:

db.grades.aggregate([
{$unwind: {path: "$scores"}}, 
{$project: {
  student_id:1,
  class_id:1,
  type:"$scores.type",
  scores:"$scores.score"}}, 
{$match: {type:"homework"}}, 
{$group: {
  _id: "$class_id",
  assignment_score: {$avg: "$scores"},
  scores: {$addToSet: "$scores"}}}, 
{$project: {
  _id: 0,
  class_id: "$_id",
  assignment_score: {$round: ["$assignment_score", 2]},
  scores: 1}}, 
{$sort: {class_id: 1}}])

假設我們現在想要得到每一堂課的學生作業的平均成績,和成績清單。

第一步 — unwind: 接續上面的解釋第一步我先將原本分數的陣列拆解。每一個人的成績都放在一個陣列當中,有考試有作業的成績,拆解過後就可以確定每個文檔裡面只有一個屬於某項評分的分數。

https://ithelp.ithome.com.tw/upload/images/20200915/20129829MiOgnJhjIP.png

第二步 — project: 因為拆解過後,拆解的東西會藏在scores底下,讓我們用project重新改變他的表現方式,簡單來說可以把他想成 SQL中的 Select 可以重新命名的功能。

https://ithelp.ithome.com.tw/upload/images/20200915/20129829OM4ZEmtF8s.png

https://ithelp.ithome.com.tw/upload/images/20200915/201298290Nflr35VCM.png

第四部 — group: 這個是這個查詢最重要的核心,這個函式受先要先定義用什麼來框組,這裡選擇的是用 course_id 來分組。分組過後我們針對同組的資料做兩件事情,首先我們先取得這組資料的平均數值,第二件事情是把裡面的成績 $push 推入一個空白的陣列。這樣就可以義路所有出現的成績了! 這裡的 $push 是bag operation (不會重複出現!) 若是希望數值只有唯一(例如說想要找的是沒有重複出現的班級編號,就會使用 $addToSet

https://ithelp.ithome.com.tw/upload/images/20200915/20129829T6nXhRjsqo.png

第五步 — project: 最後我們來調整呈現的資要樣態,首先把 _id 改名,並讓成績四捨五入到小數點下第二位,最後顯示成績陣列。

第六步 — sort: 然後再加上排序。

其實複雜的Mongo Aggregation Pipeline 就是把複雜的問題拆成片段,把手算的過程獨立的分開,再把它寫成小的函式,逐一去運行。相較於以前只有命令提式字元可以寫MongoDB的指令,現在讀者可以透過 Compass 可視化中間的步驟結果,慢慢成為 MongoDB 大師。


上一篇
Day 14 — 今晚我想來點 MongoDB 的資料
下一篇
Day 16 — MongoDB 導入資料、更新資料、刪除資料
系列文
與資料庫共舞20

尚未有邦友留言

立即登入留言