在SQL反模式這本書中以產品和產品聯繫人說明瞭此反模式。 開始的時候一個產品只有一個產品聯繫人,一個產品聯繫人需要負責多個產品, product_id(產品id)和account_id(產品聯繫人id)是多對一的關係,表設計如下: 目標:變成多對多關係 隨著業務的發展, 一個產品可能存在多個產品聯繫 ...
在SQL反模式這本書中以產品和產品聯繫人說明瞭此反模式。
開始的時候一個產品只有一個產品聯繫人,一個產品聯繫人需要負責多個產品,
product_id(產品id)和account_id(產品聯繫人id)是多對一的關係,表設計如下:
目標:變成多對多關係
隨著業務的發展, 一個產品可能存在多個產品聯繫人,即產品和聯繫人需要是多對多的關係。
為了盡少的改動,即把account_id修改varchar類型,把所有的產品聯繫人Id用逗號分隔一起存儲在account_id欄位,即存儲多值的做法,表設計如下
存儲多值問題
但存儲多值的設計會帶來如下查詢、更新和數據完整性的問題:
- 查詢某個聯繫人負責的所有產品信息困難
- 查詢某個產品對應的聯繫人詳細信息困難
- 執行聚合查詢困難,如COUNT(),SUM(),AVG(),比如統計各產品的聯繫人數量
- 更新產品的聯繫人困難
- 驗證產品聯繫人id困難,varchar類型可以輸入非整形等,沒有外鍵約束
- 長度限制,當某個產品的所有聯繫人id連接起來超過100時,意味著需要欄位長度,無法確定最長列表
所以,在設計多對多邏輯關係時,如果一個欄位需要存儲多值,需要避免此種反模式。
解決方案
創建一張交叉表,來保存多對多的關係,表設計如下:
這樣前面的問題都可以比較簡單的解決,採用Contacts.account_id做索引的查詢效率比逗號分隔字元串高效,
還可以在Contacts表中增加一些其他欄位,如添加聯繫人日期,再比如聯繫人的優先順序等
反模式適用場景
這個反模式個人用過,是在設計一個配置表的時候,包含配置項和配置值兩個欄位,在配置值里存儲了多值。
比如查詢關鍵字配置,配置項欄位值為查詢關鍵字,配置值欄位值就存儲了很多關鍵字,因為多值的記錄總共就一條,所以程式比較容易控制。
另外如果作為存儲過程的參數,為了是實現in查詢時,讓參數存儲多值時,如果輸入值的長度超過參數長度,會出現截斷問題導致程式結果異常,這個需要當心的。
所以在反模式書中也強調並不是說反模式不能使用,是有適應場景的。
存儲多值的適應場景:
- 如果應用程式接收的輸入就是帶逗號分隔的
- 只需要存儲和使用,不會進行部分修改
- 不需要對其做複雜的查詢