Logo

新人日誌

首頁關於我部落格

新人日誌

Logo

網站會不定期發佈技術筆記、職場心得相關的內容,歡迎關注本站!

網站
首頁關於我部落格
部落格
分類系列文

© 新人日誌. All rights reserved. 2020-present.

資料庫架構設計入門:一對一、一對多、多對多什麼時候該拆表?

最後更新:2026年1月12日資料庫

我們已經學了三種正規化形式:

  • 第一正規化:表單要有主鍵,同一欄位只能存一筆資料
  • 第二正規化:相依性高的欄位要拆表
  • 第三正規化:可以從其他欄位推算出來的欄位要刪掉

也學了四種欄位限制:UNIQUE、NOT NULL、PRIMARY KEY、FOREIGN KEY。

現在問題來了:一對一、一對多、多對多的關係,什麼時候該拆表?

在學完正規化之後,你可能會有個直覺:什麼時候該拆表、什麼時候不用拆?

一對一的關係,通常不拆表。

例如:一個人只有一個身分證字號。這種情況下,把身分證字號放在人物表裡就好,不需要另外建一張「身分證表」。

一對多的關係,通常也不拆表。

例如:一個部門有很多員工,但一個員工只屬於一個部門。這種情況下,我們在員工表加一個「部門」欄位就好。不需要另外建一張「員工部門對照表」。

多對多的關係,通常要拆表。

例如:一個學生可以選很多門課,一門課也有很多學生選。這種情況下,我們沒辦法在學生表加一個「選的課」欄位(因為有很多門),也沒辦法在課程表加一個「選課的學生」欄位(因為有很多人)。所以我們需要另外建一張「選課表」,記錄「哪個學生選了哪門課」。

這個直覺大致正確,但實際上要看情境。讓我們用一個例子來說明。

一對一的關係就不拆表嗎

假設我們要記錄這個資訊:「David 是一個短髮的程式講師」。

你可能會這樣設計:

人物 ID(PK)姓名髮型職業
1David短髮講師
姓名David
髮型短髮
職業講師

看起來沒問題,一張表搞定。

講師有教授領域:一對一也能拆表

但如果我們在設計的是一個「線上教學系統」呢?

講師除了職業之外,還有一個很重要的資訊:教授領域。

人物 ID(PK)姓名髮型職業教授領域
1David短髮講師程式
2Emily長髮講師哲學
3Frank短髮學生???
姓名David
髮型短髮
職業講師
教授領域程式
姓名Emily
髮型長髮
職業講師
教授領域哲學
姓名Frank
髮型短髮
職業學生
教授領域???

問題來了:Frank 是學生,不是講師,那「教授領域」要填什麼?

填 NULL?可以,但這樣每個學生的「教授領域」都是 NULL,有點浪費:

人物 ID(PK)姓名髮型職業教授領域
1David短髮講師程式
2Emily長髮講師哲學
3Frank短髮學生NULL
4Grace長髮學生NULL
5Henry短髮學生NULL
6Ivy長髮學生NULL
姓名David
髮型短髮
職業講師
教授領域程式
姓名Emily
髮型長髮
職業講師
教授領域哲學
姓名Frank
髮型短髮
職業學生
教授領域NULL
姓名Grace
髮型長髮
職業學生
教授領域NULL
姓名Henry
髮型短髮
職業學生
教授領域NULL
姓名Ivy
髮型長髮
職業學生
教授領域NULL

如果學生有一百個,就會有一百個 NULL。這個欄位對學生來說根本沒有意義。

而且,「教授領域」跟「職業是講師」這件事有很強的相依性:

  • 只有講師才有教授領域
  • 如果是講師,就應該要有教授領域

這種情況下,我們可以考慮拆表:

人物表:

人物 ID(PK)姓名髮型職業
1David短髮講師
2Emily長髮講師
3Frank短髮學生
姓名David
髮型短髮
職業講師
姓名Emily
髮型長髮
職業講師
姓名Frank
髮型短髮
職業學生

講師表:

講師 ID(PK)人物 ID(FK)教授領域
11程式
22哲學
人物 ID(FK)1
教授領域程式
人物 ID(FK)2
教授領域哲學

現在「教授領域」只會出現在講師表裡,不會有一堆 NULL。

這兩張表要怎麼看?

講師表裡的「人物 ID」是一個外部鍵,它指向人物表的「人物 ID」。

例如,講師表的第一筆資料:

  • 講師 ID = 1
  • 人物 ID = 1 → 對應到人物表的第一筆,也就是 David
  • 教授領域 = 程式

所以這筆資料的意思是:「David 是一個講師,他教的領域是程式。」

同樣的,講師表的第二筆:

  • 人物 ID = 2 → 對應到人物表的 Emily
  • 教授領域 = 哲學

意思是:「Emily 是一個講師,她教的領域是哲學。」

而 Frank 是學生,他不在講師表裡,所以不需要填「教授領域」這個欄位。

結論:一對一的關係,如果「一」有額外的性質,而且這個性質跟某個條件有很強的相依性,就可以考慮拆表。

一對多的關係就不拆表嗎

接續上面的例子,假設現在有更多講師加入:

講師 ID(PK)人物 ID(FK)教授領域
11程式
22哲學
34程式
45程式
人物 ID(FK)1
教授領域程式
人物 ID(FK)2
教授領域哲學
人物 ID(FK)4
教授領域程式
人物 ID(FK)5
教授領域程式

講師跟教授領域是什麼關係?

  • 一個領域可以被很多講師教(程式被三個講師教)
  • 一個講師目前只教一個領域

這是一個多對一的關係(多個講師對一個領域)。

按照之前學的,多對一的關係,我們把「一」記錄在「多」的表裡。所以「教授領域」放在講師表裡,看起來沒問題。

領域有熱度:違反第二正規化就該拆

但有一天,老闆說:「我想幫每個領域加一個『熱度』,表示這個領域受不受歡迎。」

例如:程式領域很熱門,哲學領域比較冷門。

如果我們直接加在講師表裡:

講師 ID(PK)人物 ID(FK)教授領域領域熱度
11程式熱門
22哲學冷門
33程式熱門
人物 ID(FK)1
教授領域程式
領域熱度熱門
人物 ID(FK)2
教授領域哲學
領域熱度冷門
人物 ID(FK)3
教授領域程式
領域熱度熱門

問題來了:David 和 Frank 都教程式,「領域熱度」都是「熱門」。

這違反了第二正規化:「領域熱度」跟「教授領域」有很強的相依性,跟「講師」沒有關係。

所以我們應該拆表:

領域表:

領域 ID(PK)領域名稱熱度
1程式熱門
2哲學冷門
領域名稱程式
熱度熱門
領域名稱哲學
熱度冷門

講師表:

講師 ID(PK)人物 ID(FK)領域 ID(FK)
111
222
331
人物 ID(FK)1
領域 ID(FK)1
人物 ID(FK)2
領域 ID(FK)2
人物 ID(FK)3
領域 ID(FK)1

現在「熱度」只記錄在領域表裡,不會重複。

結論:一對多的關係,如果「一」有自己的性質需要記錄,就應該拆表。

在這個例子裡,「領域」是「一」,「講師」是「多」。當領域有自己的性質(熱度)需要記錄時,我們就把領域拆成獨立的一張表。

這樣做的好處是:

  • 領域的熱度只記錄一次,不會重複
  • 如果要修改某個領域的熱度,只要改一個地方
  • 符合第二正規化:相依性高的欄位放在一起

多對多的關係就一定要拆表嗎

接續上面的例子,老闆又說:「我想讓一個講師可以教多個領域。」

沒問題,講師表改成這樣:

講師 ID(PK)人物 ID(FK)領域 ID(FK)
111
222
312
人物 ID(FK)1
領域 ID(FK)1
人物 ID(FK)2
領域 ID(FK)2
人物 ID(FK)1
領域 ID(FK)2

David(人物 ID = 1)現在可以教程式(領域 ID = 1)和哲學(領域 ID = 2)。

這是一個多對多的關係:

  • 一個講師可以教多個領域
  • 一個領域可以被多個講師教

按照正規化的原則,多對多的關係應該要拆表。目前這樣的設計是符合正規化的。

結構不穩定時可以用 JSON 保留彈性

但老闆又來了:「我想讓『熱度』不只跟領域有關,還要跟講師有關。」

什麼意思?

原本我們認為「熱度」是領域的性質:程式領域很熱門,哲學領域比較冷門。

但老闆說:「不對,熱度不只跟領域有關,還跟誰來教有關。」

例如,同樣是教程式:

  • David 是知名講師,他教的課很多人買,熱度高
  • Grace 是新手講師,他教的課比較少人買,熱度低

同一個領域,不同講師教,熱度不一樣。

這表示「熱度」不再只是領域的性質,而是「講師 + 領域」這個組合的性質。

如果我們繼續用拆表的方式,會變得很複雜:

  • 領域表有領域的基本熱度
  • 講師領域表有講師教這個領域的熱度
  • 兩個熱度要怎麼整合?

而且老闆可能明天又改主意…

這時候,如果結構還不穩定,我們可以選擇暫時不拆表,用比較彈性的方式記錄:

講師 ID(PK)人物 ID(FK)領域(JSON)
11[{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}]
22[{“name”: “哲學”, “熱度”: “低”}]
人物 ID(FK)1
領域(JSON)[{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}]
人物 ID(FK)2
領域(JSON)[{“name”: “哲學”, “熱度”: “低”}]

這違反了第一正規化(同一欄位存多筆資料),但在結構不穩定的情況下,這樣做可以保留彈性。

我們來看一下這個表格:

講師 ID(PK)人物 ID(FK)領域(JSON)
11[{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}]
22[{“name”: “哲學”, “熱度”: “低”}]
人物 ID(FK)1
領域(JSON)[{“name”: “程式”, “熱度”: “高”}, {“name”: “哲學”, “熱度”: “中”}]
人物 ID(FK)2
領域(JSON)[{“name”: “哲學”, “熱度”: “低”}]

第一筆資料是 David(人物 ID = 1):

  • 他教兩個領域:程式和哲學
  • 他教程式的熱度是「高」
  • 他教哲學的熱度是「中」

第二筆資料是 Emily(人物 ID = 2):

  • 她只教一個領域:哲學
  • 她教哲學的熱度是「低」

你會發現,同樣是教哲學,David 的熱度是「中」,Emily 的熱度是「低」。這就是老闆要的:熱度跟講師有關,不只是跟領域有關。

這種做法的好處是:老闆改主意的時候,不用改資料庫結構,只要改 JSON 的內容就好。

等到老闆想清楚了,結構穩定了,再來拆表也不遲。

結論:多對多的關係,正規化原則下應該拆表。但如果結構還不穩定,可以暫時用彈性的方式記錄,等穩定後再拆。

重點整理

讓我們回顧一下這篇文章學到的內容:

一對一的關係

直覺做法:不拆表,放在同一張表就好。

什麼時候要拆表:當「一」有額外的性質,而且這個性質跟某個條件有強相依性。

例如:講師有「教授領域」這個性質,但只有講師才有教授領域,學生沒有。這時候把講師表拆出來,避免一堆 NULL。

一對多的關係

直覺做法:不拆表,在「多」的那張表加一個欄位就好。

什麼時候要拆表:當「一」有自己的性質需要記錄。

例如:多個講師教同一個領域。本來在講師表加一個「教授領域」欄位就好。但當領域有自己的性質(熱度)需要記錄時,就應該把領域表拆出來,避免違反第二正規化。

多對多的關係

直覺做法:拆表,建立一張關聯表。

什麼時候可以不拆:當結構還不穩定,老闆還在改主意的時候。

例如:講師和領域是多對多關係,而且熱度跟「講師 + 領域」的組合有關。如果結構還不穩定,可以先用 JSON 保留彈性,等穩定後再拆表。

最重要的觀念:不要一聽到一對一、一對多、多對多,就覺得一定要怎麼做。

要考慮的因素包括:

  • 這個「一」有沒有額外的性質?
  • 這個性質跟什麼條件有關?
  • 現在的結構穩定嗎?還是還在探索階段?

正規化是原則,不是鐵律。理解原則背後的原因,才能在實際情境中做出正確的判斷。

目前還沒有留言,成為第一個留言的人吧!

發表留言

留言將在審核後顯示。

資料庫

目錄

  • 一對一的關係就不拆表嗎
  • 講師有教授領域:一對一也能拆表
  • 一對多的關係就不拆表嗎
  • 領域有熱度:違反第二正規化就該拆
  • 多對多的關係就一定要拆表嗎
  • 結構不穩定時可以用 JSON 保留彈性
  • 重點整理
  • 一對一的關係
  • 一對多的關係
  • 多對多的關係