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」**當範例。

  • 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。
  • 用哪一邊放陣列,一樣看查詢主體,以及哪一邊的陣列不會成長到失控。
    • 例如一個作者頂多寫幾十本書(可控),但一本書的作者數也可控——兩邊都不會爆,放哪邊看查詢方向。

兩種分析關聯的方式(講師總結)

講師把前兩個範例歸納成兩種思路:

  1. 透過某個欄位是「ID / 純量」的形式去表達關聯(存參照)。
  2. 透過內嵌(embed)物件的形式把關聯資料放在一起。

一對多本身又細分:

  • 一對多:父端陣列 or 子端純量(如上)。
  • 多對多:任一邊放陣列即可,省掉關聯式的第三張表。

小結

關聯MongoDB 表達要點
1:1link(存對方 _id)或 embed一起讀就 embed;要獨立查就 link
1:N父端 ID 陣列 or 子端純量 ID「多」的一邊會不會爆 16MB 是關鍵
N:M任一邊放 ID 陣列不用建第三張 mapping 表

「link 還是 embed」的決策準則留到 04 文件設計基礎 詳談。

→ 下一篇:03 Payload 與 Process 欄位