技術世界中的許多不同角色都會將數據標準化作為許多項目的常規部分。開發人員、數據庫管理員、領域建模者、業務利益相關者以及更多人在規范化過程中取得了進展,就像他們呼吸一樣。然而,看起來如此不可或缺的東西會變得過時嗎?

隨著數據庫環境變得更加多樣化,硬件變得更加強大,我們可能想知道是否還需要數據規范化的實踐。我們是否應該擔心優化數據存儲和查詢以便返回最少的數據量?或者,如果我們應該這樣做,某些數據結構是否比其他數據結構更能解決這些問題?

在本文中,我們將回顧數據標準化的過程,并評估何時需要此過程,或者它是否仍然是數字化存儲和檢索數據的必要部分。

什么是數據標準化?

數據規范化正在優化關系數據庫中的數據結構,以確保數據完整性和查詢效率。它通過將數據經過一系列步驟來標準化結構(范式)來減少冗余并提高準確性。從本質上講,數據規范化有助于避免插入、更新和刪除數據異常。這些異常在創建新數據、更新現有數據或刪除數據時發生,并對保持數據值同步(完整性)造成挑戰。當我們逐步完成規范化過程時,我們將詳細討論這一點。

這些步驟需要驗證鍵(相關數據的鏈接)、將不相關的實體與其他表分開,以及將行和列作為統一的數據對象進行檢查。雖然范式步驟的完整列表相當嚴格,但我們將重點關注商業實踐中最常用的范式:第一范式、第二范式和第三范式。其他范式主要用于學術和統計學。范式步驟必須按順序完成,在前一個范式完成之前我們不能移動到下一個范式。

我們如何進行數據標準化?

由于我們有三種范式來獲取數據,因此我們將分為三個步驟來完成此過程。它們如下:

  1. 第一范式 (1NF)
  2. 第二范式 (2NF)
  3. 第三范式 (3NF)

一位大學數據庫教授教我的班級記住三種范式:“關鍵,整個關鍵,除了關鍵之外什么都沒有”(就像在法庭上宣誓真相一樣)。我不得不刷新本文的一些正常形式的詳細信息,但這個基本短語一直困擾著我。希望它也能幫助您記住它們。

我最近發現了一個咖啡店數據集,它似乎很適合我們用作標準化數據集的示例。通過對此處的示例進行一些調整,我們可以逐步完成該過程。

非規范化數據

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

交易日期

transaction_time

instore_yn

客戶

loyalty_num

line_item_id

產品

數量

單價

promo_item_yn

2019-04-01

12:24:53

Y

卡米爾·泰勒

102-192-8157

1

哥倫比亞中度烘焙咖啡

1

2.00

N

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

1,2

牙買加咖啡河 Sm,燕麥烤餅

1,1

2.45,3.00

N,N

2019-04-01

16:44:46

Y

斯圖爾特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

1,2

卡布奇諾 Lg、特大咸味烤餅

2,1

4.25,3.75

N,N

該數據包含公司的銷售收據,最初發布在 Kaggle Coffee Shop 示例數據存儲庫,盡管我還創建了一個 今天帖子的 GitHub 存儲庫。上面顯示的數據顯示了向客戶訂購的產品的銷售額。

為什么這個數據有問題?前面,我們提到規范化來解決插入、更新和刪除異常。如果我們嘗試向此數據插入新行,則可能會創建重復行,或者更糟糕的是,必須收集有關客戶、產品和收貨日期/時間的所有信息才能創建它。如果我們需要更新或刪除收據上購買的產品,我們需要對每個產品列中的列表進行排序以搜索值。那么讓我們看看如何通過標準化這些數據來提高冗余和完整性。

第一范式:密鑰

對于“鍵、整個鍵,只有鍵”的第一步,表應該有一個主鍵(單個列或一組列),以確保行是唯一的。每個行中的列也應僅包含單個值;即沒有嵌套表。

我們的示例數據集需要一些工作才能達到 1NF。雖然我們可以通過日期/時間或日期/時間/客戶的組合來獲取唯一行,但引用具有某種生成的唯一值的行通常要簡單得多。我們通過在收據表中添加一個 transaction_id 字段來實現這一點。

還有幾行訂購了多個項目(transaction_id 156 和 199),因此有幾列的行項目具有多個值。我們可以通過將具有多個值的行分成單獨的行來糾正此問題。

1NF 數據

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客戶

loyalty_num

line_item_id

產品

數量

單價

promo_item_yn

150

2019-04-01

12:24:53

Y

卡米爾·泰勒

102-192-8157

1

哥倫比亞中度烘焙咖啡

1

2.00

N

156

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

1

牙買加咖啡河 Sm

1

2.45

N

156

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

2

燕麥烤餅

1

3.00

N

165

2019-04-01

16:44:46

Y

斯圖爾特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

199

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

1

卡布奇諾 Lg

2

4.25

N

199

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

2

巨型美味烤餅

1

3.75

N

使用此數據,復合(多列)鍵通過 transaction_idline_item_id 的組合唯一標識一行,作為單個收據不能包含多個訂單項 #1。如果我們將表簡化為這些主鍵值,則可以看到以下數據。

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

transaction_id

line_item_id

150

1

156

1

156

5

165

1

199

1

199

5

這兩個值的每個組合都是唯一的。我們已將第一范式應用于數據,但仍然存在一些潛在的數據異常。如果我們想要添加新收據,我們可能需要創建多行(取決于它包含多少行項目),并在每行上重復交易 ID、日期、時間和其他信息。更新和刪除會導致類似的問題,因為我們需要確保所有受影響的行數據保持一致。這就是第二范式發揮作用的地方。

第二范式:整個密鑰

第二范式確保每個非鍵列完全依賴于整個鍵。對于具有多個列作為主鍵的表(例如我們的咖啡收據表),這更值得關注。這是我們的數據的第一范式:

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客戶

loyalty_num

line_item_id

產品

數量

單價

promo_item_yn

150

2019-04-01

12:24:53

Y

卡米爾·泰勒

102-192-8157

1

哥倫比亞中度烘焙咖啡

1

2.00

N

156

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

1

牙買加咖啡河 Sm

1

2.45

N

156

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

2

燕麥烤餅

1

3.00

N

165

2019-04-01

16:44:46

Y

斯圖爾特·努涅斯

796-362-1661

1

早晨日出柴 Rg

2

2.50

N

199

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

1

卡布奇諾 Lg

2

4.25

N

199

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

2

巨型美味烤餅

1

3.75

N

我們需要評估每個非關鍵字段,看看是否有任何部分依賴關系;即,該列僅依賴于鍵的一部分而不是整個鍵。由于 transaction_idline_item_id 構成了我們的主鍵,因此我們從 transaction_date 字段開始。交易日期確實取決于交易ID,因為同一交易ID不能在另一天再次使用。但是,交易日期根本不取決于行項目 ID。訂單項可以跨交易、跨天、甚至跨客戶重復使用。

好的,我們已經發現該表不遵循第二范式,但是讓我們檢查另一列。客戶欄呢?客戶不依賴于交易 ID 和行項目 ID。如果有人給我們一個交易 ID,我們就會知道哪個客戶進行了購買,但如果給我們一個行項目 ID,我們就不會知道該收據屬于哪個客戶。畢竟,多個顧客可能在他們的收據上訂購了一件、兩件或六件商品。客戶鏈接到交易 ID(假設多個客戶無法拆分收據),但客戶不依賴于行項目。我們需要修復這些部分依賴關系。

最直接的解決方案是為訂單行項目創建一個單獨的表,將僅依賴于 transaction_id 的列保留在收據表中。第二范式中更新后的數據如下所示。

收據

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

transaction_id

交易日期

transaction_time

instore_yn

客戶

loyalty_num

150

2019-04-01

12:24:53

Y

卡米爾·泰勒

102-192-8157

156

2019-04-01

12:30:00

N

格里菲斯·林賽

769-005-9211

165

2019-04-01

16:44:46

Y

斯圖爾特·努涅斯

796-362-1661

199

2019-04-01

14:24:55

Y

阿利斯泰爾·拉米雷斯

253-876-9471

收據行項目

<表格樣式=“最大寬度:100%;寬度:自動;表格布局:固定;顯示:表格;”寬度=“自動”>
<正文>

transaction_id

line_item_id

product_id

產品

數量

單價

promo_item_yn

150

1

28

哥倫比亞中度烘焙咖啡

1

2.00

N

156

1

34

牙買加咖啡河 Sm

1

2.45

N

156

2

77

燕麥烤餅

1

3.00

N

165

1

54

早晨日出柴 Rg

2

2.50

N

199

1

41

卡布奇諾 Lg

2

4.25

N

199

2

79

巨型美味烤餅

1

3.75

N