進度條

「不是工程師」資料庫正規化是什麼? 先從第一正規化(1NF)開始吧!(database normalization)

資料庫設計很難?都不實作練習當然難!先用這簡單的範例來了解吧!

作者: Vincent Ke 更新日期:

 

在關聯式資料的世界裡,其實DB table就像是一連串複雜的Excel表格,去做互相索引的動作,來關聯出整個網站架構上的資料,但倘若資料庫內的資訊雜亂無章,或是一堆重複無意義的資料,不僅對編輯上會造成負擔外,更會造成系統效能的增加俗話說,好的backend 帶你上天堂,糟糕的Backend 讓你每天bug一直查,但Backend的好壞與否,其實最重要的核心,就在於Data base table的設計,尤其是在使用關聯式資料庫的架構下,DB table是否有做過正規化,就顯得特別重要

 

 

在關聯式資料的世界裡,其實DB table就像是一連串複雜的Excel表格,去做互相索引的動作,來關聯出整個網站架構上的資料,但倘若資料庫內的資訊雜亂無章,或是一堆重複無意義的資料,不僅對編輯上會造成負擔外,更會造成系統效能的增加

 

 

校正小編補充:

資料庫正規化是關聯式資料庫(Relational Database)的概念,NoSQL基本上沒有這個概念,畢竟NoSQL光是JSON形式的儲存就已經違反本文標題的第一正規化(1NF),因此設計理念、使用概念、目的、優勢劣勢都截然不同。需要搭判使用情境來判斷。
 

 

 

background-code-coder-177598.jpg

 

於是Edgar Frank Codd在1970年提出了資料庫正規化(database normalization或是稱標準化)的概念,目的是減少資料庫中資料冗餘,增進資料的一致性,並增加資料查找或新增的效能,避免系統儲存空間上的浪費。其中最有名的就是第一(1NF)到第三正規化(3NF),以下小編會特別用生活實例,來對1NF到3NF做出簡單的介紹。(本文先講第一正規化 1NF)

 

 

close-up-code-coding-374559.jpg

 

 

相信大家都有使用過Excel記帳的概念,而每個人記帳的方式都不全相同,而正規劃的目的,就是為了讓這些記帳的方式有一定的邏輯與條理,來做到表格呈現的簡單化、邏輯化。

 

 

user date pay item shop
小明 9/30 100, 200 螺絲, 軸承 好記
小華 10/1 -200 廠商退款 興記

 

校正小編補充:

如果你英文苦手的話
user : 使用者
date: 日期
pay: 付款
item: 物件
shop: 商店

後面會用到的
key: 鑰匙
value: 值
 

 

 


這樣的Excel資料表上看似合理,但不知道大家有沒有發現,這裡有些欄位單一資料其實包含了兩個以上的數值或內容,儘管看的人一目瞭然,但畢竟系統不是人腦,都是一個指令一個動作的,儘管我們在pay和item上做出了逗號分隔,但對電腦而言,這樣的方式還是無法準確的區分。

 

校正小編補充:

這邊指的是pay 出現了"100, 200"的值,而item出現了"螺絲, 軸承"這樣的兩個值於同一個欄位內。如果對於寫程式已經有一點概念的同學應該會發現應用層面的問題。

如果今天要紀錄100這樣的數字,我們可以使用Integer(整數 Int), 但是要處理"100, 200", 我們則必須使用Array 陣列紀錄成[100, 200] 或是直接用String 字串 "100, 200"紀錄。用字串絕對是不合理的做法,這樣沒辦法加總。如果用陣列的話,再加總的時候必須要判斷為Int或是Array,如果是Array就必須補上個迴圈(很多函式也只是把迴圈包起來而已,內部進行的還是迴圈,例如map)。

這樣很容易會讓應用層程式邏輯變複雜許多,而且沒必要。

更不用說正確正規化後可以讓資料庫幫你做加總而不需再應用層實作。

 



而第一正規化裏最重要的概念有兩個:定義主鍵值(primary key)以及單一值

 

先來整理值吧,從該表上可看到 pay 與item上都有著多筆資料在同一欄位中紀錄的問題,所以我們先把表格重新攤開

 

 

user date pay item shop
小明 9/30 100 螺絲 好記
小明 9/30 200 軸承 好記
小華 10/1 -200 廠商退款 興記


 

這樣就達到每一個對應的key裡都只有一個Value,對系統而言資料的辨識度也高出許多,但如果今天我對系統下達的指令是「刪除」小明的帳,結果發現小明現在卻有兩筆帳,這時候該怎麼辦呢?

 

這時候就是第一正規化最重要的概念—「定義主鍵值 Primary Key」就派上用場啦,主鍵值的定義,是資料庫表中對儲存資料物件是屬於唯一,並且可完整標識的資料列或屬性的值,其不能是重複也不能是空值。若以記帳表格來說,購買人、購買日期或是付款金額等等,都因為具備可重複性,所以都不具備主鍵值的條件。

 

 

black-friday-fashion-friends-1345082.jpg

 

所以我們來幫這張表上加上主鍵值吧,小編使用每一筆記帳的”順序id“來當作主鍵值,只要是每一個需要登進帳本的項目,就必須把id累加上去,這樣既不重複,也具備識別性,若是在系統設計上,可以讓他成為一個自動疊加的純數字

 

id user date pay item shop
1 小明 9/30 100 螺絲 好記
2 小明 9/30 200 軸承 好記
3 小華 10/1 -200 廠商退款 興記

 

 

加上去後一切就清楚明瞭許多,不管是新增、查找或刪除,我們一切都可以依據id 來做出識別和溝通,就算是家族記帳,資料越漲越大,我們也可以很明確的溝通說是哪一筆的帳目有問題。

 

校正小編補充:

這邊原作者其實是寫 post_id 而非 id,校正小編修改成id。原因是因為關聯性資料庫的欄位column是屬於某一個Table之下。如果今天Table已經叫Post的形況下,其實是不需要取名primary key叫做post_id的,直接叫"id"即可。
  
在有一些ORM系統裡面(例如Ruby on Rails),預設Table name + "id"是用於Foreign Key的。當然這並不是絕對,只是校正小編認為這樣比較直覺。


另外一提,如果你在關聯性資料庫存JSON基本上就是違反了第一正規化(1NF),但是違反不見得就是"死刑",這點會在以後的文章探討。
 



雖然用系統自動增長的純數字,當作主鍵值是一個不錯的選擇,但對許多大型系統而言,這樣的方式許多風險,例如容易經過推敲來了解到系統的資料量等。所以也會有人利用字母+流水號,或是TimeStamp + GUID等方式來產生,但不論使用何種產生方式,依照系統架構與效能需求,來確定主鍵值的識別性與不重複性,仍然是第一正規化最重要的概念。


 

校正小編補充:

id不用流水號的原因可能很多,不同目的的Table表其實可以使用不同的設計。流水號其中一個優勢為可以直接看ID看出大概有多少筆資料,ID不重複的概念也很容易理解。但是如果直接顯示可能會被猜到商家營業額(單日多少單號),或是網路爬蟲很好爬 (一個迴圈掃到底)。
  
UUID / GUID 等雜湊值雖然可以有效的解決這些問題,但是基本上字數就比較多,多少會影響比對速度與儲存空間,也很難去記憶。試想當你是客服人員,請客戶回覆你訂單號碼的時候,他給你請幫我查 "ec48edee-1f5a-48f1-9a32-845e9db7cd03"。 他光打字就恨死你了,你如果用電話接聽你也會恨死工程師。

不過這些都是很簡單可以解決的問題(多開column 或是 用日期 + 部分hash值),但是如果開資料表的人沒注意到這點小地方,後面維護以及使用的人會異常痛苦。
 

 

 


 

coffee-communication-compose-1498964.jpg

 

不知道眼尖的同學們有沒有發現,這樣紀錄的方式其實很容易出現大量的重複資料。如果重複資料的資訊量又很大的話,不就很佔空間?並且在更改的時候會十分麻煩,試想一個有10萬筆紀錄的使用者改名,要手動複製貼上嗎?


 

就讓小編在下一篇文章介紹第二正規化,來解決大家的疑惑。
 

 

最後廣告一下,如果對資料庫設計和網站架設有興趣的話,可以考慮參加我們的Ruby on Rails課程。課程中包含詳細的網站架設與資料庫設計教學,內容涵蓋SQLite, MySQL, PostgreSQL,以及最後的募資平台(含資料表設計),可以參考看看喔!

線上程式教學課程: 快速開發,從頭教起的Ruby on Rails後端之旅

 


最後,如果你喜歡我們的文章,別忘了到我們的FB粉絲團按讚喔!!

Medium vincent

Vincent Ke

喜歡把混亂的事情變的簡單 用嘴巴做事其實很可以 但要結合靈活的腦袋思考 就一起來拆解吧