一開始在使用MongoDB的時候,總是在想它不像MySQL關聯資料庫一樣,可以做兩張資料表的join,要把兩個collection的資料,分別搜尋自己組合好麻煩,還是MySQL比較好。
直到我遇見了$lookup
,它的作用和關聯資料庫的join類似,可以將兩個collection資料合併再一起,傳遞給下一個pipeline,它的寫法分成兩種,一種是今天要介紹的寫法,比較陽春一點,會把join的collection資料欄位全部加進來。
另一種寫法比較複雜一點,但可以在$lookup
內寫pipeline,將要join進來的collection資料先運算,再做join的動作。
此外在進行$lookup
,如果兩個collection的資料數量都很多,強烈建議一定要設索引(index),不然搜尋資料的效能會變得超差。
例如:學生的成績這個collection有3000筆資料,而學生基本資料的collection有600筆資料,兩個collection做$lookup搜尋資料,在沒有建立索引的情況下,會搜尋資料3000×600次,十分的沒效率。
假設現在我們有學生成績資料(score)三筆
{ id: 1, student: 1, test: 1, math: 20, english: 45 },
{ id: 2, student: 2, test: 1, math: 30, english: 80 },
{ id: 3, student: 3, test: 1, math: 85, english: 83 },
學生的個人資料(profile)三筆
{ id: 1, name: "小明", sex: 0, seat_number: 23 },
{ id: 2, name: "小王", sex: 0, seat_number: 17 },
{ id: 3, name: "小美", sex: 1, seat_number: 6 }
如果老師想要將兩張資料表合併,製作成績單列印給學生帶回去,會用以下指令。
profile.aggregate([
{
$lookup: {
from: "score", // 從哪一張資料表join資料
as: "test_score", // join的資料會新增在一個欄位內,as要填就是該欄位的名稱
localField: "id", // 引用其他collection,必須有哪一個欄位是相等的(本地欄位)
foreignField: "student_id" // 引用其他collection,必須有哪一個欄位是相等的(外來欄位)
}
}
]);
得到的資料會如下
{
id: 1,
name: "小明",
sex: 0,
seat_number: 23,
test_score: [{ id: 1, student: 1, test: 1, math: 20, english: 45 }]
},
{
id: 2,
name: "小王",
sex: 0,
seat_number: 17,
test_score: [{ id: 2, student: 2, test: 1, math: 30, english: 80 }]
},
{
id: 3,
name: "小美",
sex: 1,
seat_number: 6,
test_score: [{ id: 3, student: 3, test: 1, math: 85, english: 83 }]
}
join加入的資料會用陣列包裹起來,如果想要讓資料再精簡一點,只取我們想要的欄位,必須在寫一個$project
和$addFields
的pipeline,整理要回傳的資料欄位。
例如:老師成績部分只想保留數學和英文成績,個人資料只想要留下姓名和座號。
我們可以利用$arrayElemAt
取得特定陣列的元素,後面接一個陣列,第一個item填要取資料的陣列欄位名稱,第二個item填要取的元素位置。
profile.aggregate([
{
$lookup: {
from: "score",
as: "test_score",
localField: "id",
foreignField: "student_id"
}
},
{
$$addFields: {
sore: { $arrayElemAt: ["$test_score", 0] }
}
},
{
$project: {
name: 1,
seat_number: 1,
math: "$sore.math", // 取得sore物件中math的值
english: "$sore.english"
}
}
]);
回傳的資料會是
{ id: 1, name: "小明", seat_number: 23, math: 20, english: 45 },
{ id: 2, name: "小王", seat_number: 17, math: 30, english: 80 },
{ id: 3, name: "小美", seat_number: 6, math: 85, english: 83 }
ps.如果很想知道每一個pipeline處理完的資料,到底會長怎樣,可以自己console.log()出來看一下。
本篇文章同步放在我的部落格,大家有空可以進來逛逛