[資料庫] 關聯介紹 一對一、一對多、多對多

資料庫

前言

會想要寫這篇是因為最近在摸php框架laravel

在ORM的地方間接學到了資料庫的Relationships

才發現以前好傻好天真,竟然規劃出這樣的table…

別懷疑我真的幹過這種蠢事@@

posts跟tags其實是多對多的關係

應該要建立一個intermediary table才是正確的做法((以下會詳細說明

因此,必須好好筆記一下最近學到的資料庫Relationships

 

關聯介紹

假設有個部落格文章在資料庫裡的table長這樣,把所有文章所需的資料都集中在一個table

不難發現,文章的tag有重複的現象

而且如果今天,我又新增了一篇文章,tag不小心手誤打成了Databases

是不是又歸成了新的tag了

或者,我今天想要有兩個tags,三個tags,四個tags

那是不是又要重蹈覆轍建立好多columns了@@

因此,在現在的資料庫中,普遍都會把共同常用的資料獨立成新的table

再利用主鍵(primary key)與外鍵(foreign key)串起table與table間的關係

主鍵:
可以拿來識別所有資料的column,以剛剛的table來說就是id。id=1可以找到第一筆資料,以此類推。
外鍵:
其他table的主鍵,拿來給目前的table參考過去用。

如此一來就不會發生手誤打錯的情形了

這種資料庫的形式就叫做「關聯式資料庫」。

 

關聯種類


 

一對一關聯

甲乙兩個table裡面各自存在一筆記錄,可以使用其主鍵,對應至另一個table的一筆記錄。

拿下圖當例子,假設我有access兩個table的權限,只要SELECT學號(主鍵)107000001再做JOIN

就可以正確且只有得到小明的完整資料

(不會是其他人的,因為學號是主鍵,不會重複)

當然有些人會問說為什麼不放在同個table省去JOIN的麻煩

table分開設計的好處是可以直接設定該table被存取的權限

讓不相干的人只能存取到學生的名單

只有老師可以查看學生的個人資料

舉其它例子,像是公司內人資部門有員工基本資料

財務部門有員工薪資資料

有最高權限的總經理可以以員工代號去SELECTJOIN出這名同仁的完整資料(舉這樣的例子好像怪怪的…)


 

一對多關聯

甲table的一筆記錄可以對應到乙table的多筆記錄;
但,乙table的一筆記錄只能對應到甲table的一筆記錄。

另一個相同的名詞叫多對一,其實一對多與多對一只有資料表在表達時順序上的差別而已

如上圖,我們把科系名稱獨立成一個table

這樣就不會發生系助不小心key in科系時打錯字,造成用科系SELECT不到正確資料的情況

或如果某個科系被迫改名時,只要改科系的那個table一處即可(我怎麼都舉些怪怪的例子…)


 

多對多關聯

甲table的一筆記錄可以對應到乙table的多筆記錄;
乙table的一筆記錄也可以對應到甲table的多筆記錄。

聽起來很饒舌沒關係,我們拿一開始的table舉例,看到2nd Post這筆記錄

他的tag1對應到名叫SQL的tag
tag2對應到名叫PHP的tag
tag3對應到名叫Database的tag

同樣地,SQL這個tag對應到title叫First Post的post
以及2nd Post的post

這種雙方都互相對應多比對方記錄的模式則叫多對多關聯

我也要順便澄清上面的圖表不是正統作法

正統設計多對多關聯的方式其實是在中間建立另一個intermediary table(中文我不知道叫什麼…)

藉由posts對tag_post一對多關聯
以及tags對tag_post一對多關聯

來實現兩個table之間的多對多的relationship

 

搭配圖例再來一次

我們把原本設計很爛有重複相同用途的column(tag1、tag2、tag3)刪除

新增一個intermediary table來存放tag_id跟post_id

如此一來就可以實現兩個table多對多

而且再也不用tag1、tag2、…、tag100,以及一堆空值的格子及數量限制的問題了(歡呼)

真是可喜可賀(撒花)~

 

最後,follow上下圖的綠色、藍色、橘色箭頭再走一次
相信大家能更深刻理解這個做法的。

22 Comments

  1. 很清楚的整理! 謝謝 :):):)
    – 一樣在 Laravel 裡才學到 relation 的 Laraveler

  2. 請問在多對多中,當要改變post的tag,例如原本3項改成二項,或改變內容時,要如何下指令讓tag_post的表跟著改變呢?

    1. 如果是三項遍兩項只要在tag_post那個intermediary table裡面把不要的刪除就行了唷:)

  3. Hi Andy,
    謝謝您的文章, 讓我更了解了intermediary table,
    有個intermediary table問題想請教您, 希望您在閒暇之餘有空能指點解惑 : )

    我查了許多別人分享的經驗, 在設計database 關於折扣時 都是把折扣表 和商品表 做聯結,
    但我想要的是 直接在發票上依照節日促銷活動給予10%或是20%折扣, 例如 母親節時總額度有10%折扣,然後去顯示出Discount 有多少.

    我設計一個invoice table 和一個 promotion table,
    invoice table 中有一個filed 叫 Discount , 此discount 不會顯示折扣%數,而是顯示”折扣金額” (金額x折扣趴數), 此discount 的折扣%數 取自 promotion table 中的Discount_Percentage

    若直接將兩個表作PK 跟FK 的連結 (在Invoice table中建立一個Promotion ID當做FK), 但以我的認知是用這方法只會在invoice table中呈現出promotion ID
    (我不確定這樣想是正確還是錯誤的…)
    所以我想需要一個 中介 的表格叫 Invoice_Promotion,
    裡面有InvoiceID (FK) 跟 PromotionID(FK) 然後也有fild叫Discount_percentage,
    需要如此設計嗎? 還是這樣有點多此一舉?

    謝謝您看完我的提問

    1. 感覺有點多此一舉,因為在invoice table中已知有promotion ID當做FK了,那就可以用join的方式從invoice table的某筆記錄關聯至他是使用了哪個promotion了。

  4. After examine a few of the weblog posts on your website now, and I really like your manner of blogging. I bookmarked it to my bookmark web site listing and will probably be checking back soon. Pls try my web site as properly and let me know what you think.

  5. 我相請問 假如原本 帶有tag1 tag2 tag3 的資料表
    我需要一筆資料就查到 三個tag對應tags的name欄

    例如
    {This is the content.,Database}
    {The second content.,SQL,PHP,Database}

    但是如果使用中間表方法
    是不是SELECT到的會變成
    {This is the content.,Database}
    {The second content.,SQL}
    {The second content.,PHP}
    {The second content.Database}
    原本的第二筆資料 會查到三筆資料?
    有辦法 一次就把tag1 2 3 的name SELECT到嗎? 謝謝

    1. 沒錯,因為資料庫返回的格式是二維陣列所以會造成返回的content出現了多次。
      如果要讓傳輸內容不那麼龐大可以分兩次撈,第一次撈posts(id, title, content),第二次撈tag_post(p_id, t_id)。
      然後再遍歷tag_post把對應的t_id根據p_id放回到post的物件去。

發佈回覆給「Andy Wu」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *