02 關聯模型 Linking Models
← 01 MongoDB 介紹 | 回到 MongoDB 資料建模設計 | 下一篇 → 03 Payload 與 Process 欄位
模型之間的關聯(link 關係)不外乎三種:一對一(1:1)、一對多(1:N)、多對多(N:M)。這篇看它們在 MongoDB 裡怎麼表達。核心對照組是**「link(存 ID 參照)vs embed(直接內嵌)」**——這在 MongoDB 裡沒有外鍵(foreign key)的機制,靠的是設計約定。
全篇用**「圖書 book / 作者 author」**當範例。
Link Types(p.13)
- One-to-One Relationships
- One-to-Many Relationships
- Many-to-Many Relationships
一對一(One-to-One)
寫法 A:Linked —— 存對方的 ID(p.14)
任一邊都可以持有關聯(either side can track relationship):
book = {
_id: "B456",
title: "The Great Gatsby",
slug: "9781857150193-the-great-gatsby",
author: "A123", // book 這邊存 author 的 _id
// Other fields follow…
}
author = {
_id: "A123",
firstName: "F. Scott",
lastName: "Fitzgerald",
book: "B456" // 也可以反過來,在 author 這邊存 book 的 _id
}
// 兩個方向的查法:
authorofbook = db.authors.find({_id : book.author})
bookbyauthor = db.books.find({author : author._id})- MongoDB 沒有真正的外鍵,
author: "A123"只是一個一般欄位,靠應用約定它是參照。 - 只要在其中一邊存 ID 就能表達 1:1,不必兩邊都存。要用哪一邊,取決於你的查詢主體是誰(查 book 較多就存在 book,反之存在 author)。
寫法 B:Embedded —— 直接內嵌(p.15)
book = {
_id: "B456",
title: "The Great Gatsby",
slug: "9781857150193-the-great-gatsby",
author: { // 直接把 author 內容嵌進來
firstName: "F. Scott",
lastName: "Fitzgerald"
}
// Other fields follow…
}- 查 book 時不需要再去別的 collection 撈 author,一次就拿到,讀取最快。
- 講師觀點:1:1 若 author 幾乎總是隨 book 一起被讀、且不需要獨立查 author 名字,embed 是很自然的選擇。若之後需要「用 author 名字反查」,那就得考慮 link 或另外處理(延伸到 06 設計模式 Design Patterns 的 Attribute 討論)。
一對多(One-to-Many)
寫法 A:Array in Parent —— 父端存子的 ID 陣列(p.16)
author = {
_id: "A123",
firstName: "F. Scott",
lastName: "Fitzgerald",
books: ["B456", "B789", "B20"] // 一個作者對多本書
}
booksbyauthor = db.books.find({_id: { $in : author.books}}) // 用 $in 一次撈多本
authorofbook = db.author.find({books : book._id})- 用
$in對陣列一次查出多筆。
寫法 B:Scalar in Child —— 子端各自存父的 ID(p.17)
book1 = {
_id: "B456", title: "The Great Gatsby",
slug: "9781857150193-the-great-gatsby",
author: "A123", // 每本書存同一個作者 ID
}
book2 = {
_id: "B789", title: "This Side of Paradise",
slug: "9780679447238-this-side-of-paradise",
author: "A123",
}
authorofbook = db.authors.find({_id:book.author})
booksbyauthor = db.books.find({author:author._id})- 差別在「ID 陣列放父端」還是「純量 ID 放每個子端」。
- 怎麼選:看資料量與成長性。
- 一本書只寫一個作者,用 scalar in child 很自然。
- 但如果是「一個作者對很多書」且書量可能長到很大(幾百、上千、甚至無上限),把
books: [...]陣列放在 author 裡會讓 author document 無限成長,可能撞上 MongoDB 單一 document 16MB 上限。這種高成長的一對多,較適合把參照放在子端(scalar in child),或改用其他模式。 - 講師舉例:商品↔評論,一個商品可能有幾十萬則評論,就不該把評論全塞進商品 document。
⚠️ 16MB 限制:MongoDB 單一 document 最大 16MB。設計一對多時務必估算「多」的那邊會不會爆掉。
多對多(Many-to-Many)—— Array in either side(p.18)
book = {
_id: "B456",
title: "The Great Gatsby",
authors: ["A123", "A5"] // 一本書多個作者
// Other fields follow…
}
authorsofbook = db.authors.find({_id:{$in:book.authors}})
booksbyauthor = db.books.find({authors: author._id}) // Query against array
author = {
_id: "A123",
firstName: "F. Scott",
lastName: "Fitzgerald",
books: ["B456"] // Alternative - don't need both ends
}- 多對多可以在任一邊放陣列。
- 關鍵:不需要兩邊都維護。 關聯式資料庫的多對多要建第三張 mapping 表;MongoDB 只要在其中一邊放 ID 陣列就能表達,不必建第三個 collection。
- 用哪一邊放陣列,一樣看查詢主體,以及哪一邊的陣列不會成長到失控。
- 例如一個作者頂多寫幾十本書(可控),但一本書的作者數也可控——兩邊都不會爆,放哪邊看查詢方向。
兩種分析關聯的方式(講師總結)
講師把前兩個範例歸納成兩種思路:
- 透過某個欄位是「ID / 純量」的形式去表達關聯(存參照)。
- 透過內嵌(embed)物件的形式把關聯資料放在一起。
一對多本身又細分:
- 一對多:父端陣列 or 子端純量(如上)。
- 多對多:任一邊放陣列即可,省掉關聯式的第三張表。
小結
| 關聯 | MongoDB 表達 | 要點 |
|---|---|---|
| 1:1 | link(存對方 _id)或 embed | 一起讀就 embed;要獨立查就 link |
| 1:N | 父端 ID 陣列 or 子端純量 ID | 「多」的一邊會不會爆 16MB 是關鍵 |
| N:M | 任一邊放 ID 陣列 | 不用建第三張 mapping 表 |
「link 還是 embed」的決策準則留到 04 文件設計基礎 詳談。
→ 下一篇:03 Payload 與 Process 欄位