2015-01-16 74 views
3

假設我有一對一關係中的兩個表。我們會打電話給第一個Bros,第二個Homies。兄弟可以有多個兄弟,但其中只有一個可以成爲他的「主要人物」。數據庫設計 - 一對多關係中的「特殊」條目

(看;例如硬不要對我大喊大叫。)

我將如何表示?我可以將一個'main_man'條目放入bros表中,但是這會重複我在homies表中的條目。

我也可以在homies表中添加一個條目,但是這不會限制其他家族成爲主要人物。

有沒有適當的方法來做到這一點?只是用錯誤的方式來處理它,而用應用程序來處理它會更容易嗎?

回答

3

有兩種建模方法可供選擇。

首先,main_man也必須是homie?如果是這樣,我會在homies表上添加一個標誌。 MySQL數據類型有點不完美,但我會使用一個布爾值,我們總是映射到一個TINYINT(1) DEFAULT NULL COMMENT 'boolean'數據類型。

下一步是限制其值爲1NULL,不允許任何其他值。不幸的是,MySQL不強制執行CHECK約束,所以如果我們希望數據庫執行這個規則,我們需要實施BEFORE INSERT/BEFORE UPDATE觸發器來執行它。

最後,我們會增加一個UNIQUE約束

... ON homies (bro_id, main_man) 

就這樣,MySQL將只允許一個單行的1每個bro_id一個main_man值。

這與微軟文檔支持的NULL意義「未知」的規範模式略有偏差。在我們的實現中,我們使用NULL值表示「不,不是main_man」。允許NULL值的主要優點是SQL(通常)和MySQL特別不認爲NULL值是另一個NULL值的「重複」。 UNIQUE約束允許具有NULL值的多行。 (我覺得有改變這種行爲的一些SQL_MODE設置,但我們千萬不要去那裏。)

得到公正的homies這是一個main_man ...

WHERE main_man = 1 

,或者更簡潔,因爲我們沒有使用零表示TRUE,如果我們確信沒有其他非零值可能存在......

WHERE main_man 

另一個邏輯是非常簡單的,檢查main_man IS NULLMAIN_MAN <=> NULLORDER BY main_man, ...,如果要在客戶端上對其進行排序,則返回SELECT中的main_man列。

您可能會考慮使用MySQL ENUM數據類型,只要我們允許NULL值,並驗證MySQL將允許並在ENUM列上強制實施UNIQUE約束。 (我從來沒有嘗試過)。

這只是幾種方法之一,但它是我過去成功使用的方法之一。

-

示範

CREATE TABLE bro 
(id INT UNSIGNED NOT NULL PRIMARY KEY 
) ENGINE=INNODB; 

CREATE TABLE homie 
(id   INT UNSIGNED NOT NULL PRIMARY KEY 
, bro_id  INT UNSIGNED NOT NULL COMMENT 'FK ref bros.id' 
, main_man TINYINT(1) DEFAULT NULL COMMENT 'boolean, 1=is the main man' 
, homie_name VARCHAR(10) 
) ENGINE=INNODB; 

ALTER TABLE homie 
    ADD UNIQUE INDEX homie_UX1 (bro_id, main_man); 

ALTER TABLE homie 
    ADD CONSTRAINT FK_homie_bro FOREIGN KEY (bro_id) REFERENCES bro (id); 

TODO:添加BEFORE INSERT/BEFORE UPDATE觸發器用於main_man列限制值。

通過添加一些行來測試此操作,並檢查給定的bro_id我們不能有多於一個main_man

INSERT INTO bro (id) VALUES 
(2),(3); 

INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES 
    (11, 2, NULL, 'mr.slate') 
, (12, 2, 1, 'barney') 
; 

-- attempt to insert another main_man   
INSERT INTO homie (id, bro_id, main_man, homie_name) VALUES 
    (13, 2, 1, 'wilma') 
; 

-- Error Code: 1062 
-- Duplicate entry '2-1' for key 'homie_UX1' 

UPDATE homie SET main_man = 1 WHERE id = 11 ; 

-- Error Code: 1062 
-- Duplicate entry '2-1' for key 'homie_UX1' 

注:我忘了提及,作爲一個小的獎金,在homie_UX1指數(創建強制執行UNIQUE約束)也用於支撐外鍵,因爲bro_id是領先的列。這就是我們在添加外鍵約束之前添加索引的原因。

2

下面是建模一個一對多的關係,一個漂亮的標準方式,其中子行的一個被認爲是「特別」:

enter image description here

這種模式有以下重要屬性:

  • 除了從孩子到家長的正常FK,我們還使用父母之間的「反向」FK。
  • 我們使用識別關係,使孩子弱實體(即孩子的密鑰包含從父母遷移的密鑰)。兄弟是由他的同性戀中的他的「號碼」(BRO_NO)屬於。不同親友中的不同兄弟可能具有相同的BRO_NO。

總之,這兩個屬性確保:

  • 最多一個孩子是每個父母的每個特殊。
  • 家長不能選擇其自己孩子集外的特殊行 - 請注意HOMIE表中的FK1不僅包含MAIN_MAN_ID而且包含HOMIE_ID。

但是,在併發環境中,您必須小心如何生成BRO_NO。一些可能性:

  • 使其自動增量並與值中的「孔」一起生活。
  • 鎖定父然後用MAX + 1
  • 只需使用MAX + 1無鎖,但要準備應付鍵衝突並重新插入,如果併發事務試圖插入相同的值。

如果還有其他表引用BRO,則可以考慮添加代理鍵(例如BRO_ID)。代理鍵的優缺點見here

順便說一下,上述模型有一個變化:失去反向FK,只考慮具有最小的最小的 BRO_NO。如果您事先知道特殊兄弟,或者您不介意更新密鑰(可能會級聯變更)以將兄弟移動到頂端,這很好。


如果DBMS支持延遲的限制,FK父可以由非NULL,並確保正好一個孩子是特殊的(不只是零或一)。在存在圓形FK的情況下插入新數據時,篡改其中一個FK會打破雞與雞蛋的問題。不幸的是,MySQL不支持延遲約束。