2016-04-24 73 views
2

我有以下表格用於創建具有不同類型的任意數量的項目。兩個遞歸多對多關係之間的約束

CREATE TABLE item_types (
    id SERIAL, 
    PRIMARY KEY (id) 
    -- Other columns omitted 
); 

CREATE TABLE items (
    id SERIAL, 
    itemtype integer NOT NULL, 
    PRIMARY KEY (id), 
    FOREIGN KEY (itemtype) REFERENCES item_types (id) 
    -- Other columns omitted 
); 

items表具有遞歸許多一對多的關係稱爲item_relationship

CREATE TABLE item_relationships (
    itemid1 integer, 
    itemid2 integer, 
    PRIMARY KEY (itemid1, itemid2), 
    FOREIGN KEY (itemid1) REFERENCES items (id), 
    FOREIGN KEY (itemid2) REFERENCES items (id) 
); 

item_types表具有遞歸許多一對多的關係稱爲item_relationship_types

CREATE TABLE item_relationship_types (
    type1 integer, 
    type2 integer, 
    PRIMARY KEY (type1, type2), 
    FOREIGN KEY (type1) REFERENCES item_types (id), 
    FOREIGN KEY (type2) REFERENCES item_types (id) 
); 

現在,我想要做的就是以某種方式有,你不能意外地創建一個item_relationship是無效的,即在項目的item_types不會以任何item_relationship_type發現了一個約束。我有兩個問題。

  1. 這樣的約束是否有意義?我認爲插入錯誤的關係是一個在商業邏輯中很容易發生的錯誤,因此在數據庫中進行約束是很重要的。

  2. 真正實現約束的明智方式是什麼?

+0

注意:這裏沒有涉及遞歸;這是一種* model-> instance *繼承模式。 – wildplasser

+1

「遞歸」究竟意味着什麼?例如,當表'item_relationship_types'具有以下記錄:'(1,2),(2,3),(3,4)',這是否意味着類型1不僅與類型2相關,而且還與3和4? – krokodilko

+0

@wildplasser感謝您的澄清。 – ieyasu

回答

1

其中一個可能的辦法是擴大item_relationship_types表代理主鍵:

CREATE TABLE item_relationship_types (
    id integer SERIAL, 
    type1 integer, 
    type2 integer, 
    PRIMARY KEY (id), 
    UNIQUE (type1, type2), 
    FOREIGN KEY (type1) REFERENCES item_types (id), 
    FOREIGN KEY (type2) REFERENCES item_types (id) 
); 

和th恩添加一個外鍵指向這個代理鍵爲item_relationships表:

CREATE TABLE item_relationships (
    itemid1 integer, 
    itemid2 integer, 
    type_rel_id integer not null, 
    PRIMARY KEY (itemid1, itemid2), 
    FOREIGN KEY (itemid1) REFERENCES items (id), 
    FOREIGN KEY (itemid2) REFERENCES items (id), 
    FOREIGN KEY (type_rel_id) REFERENCES item_relationship_types (id) 
); 

你也需要創建一個觸發器,它進入type_rel_iditem_relationships表指向與不相關的這兩個項目類型條目禁止item_relationship_types表。

+0

我嘗試過觸發器變體,但事實證明它太慢了。我擴展了你的想法,而不是創建一個代理鍵,我從'item_relationship_types'將'type1'和'type2'直接添加到'item_relationships'。在'item_relationships'中,我爲'type2'和'itemid2'添加了'FOREIGN KEY(type1,itemid1)REFERENCES items(itemtype,id)'並且是相同的。然後我添加了'FOREIGN KEY(type1,type2)REFERENCES item_relation_types(type1,type2)'。這有效地處理了約束,但是不得不將'type1'和'type2'添加到'item_relations'。對此解決方案有什麼想法? – ieyasu

1
  • 這不是完美的,但它似乎工作

CREATE FUNCTION item_check_types() RETURNS TRIGGER AS 
$func$ 
BEGIN 
IF EXISTS (
     SELECT 1 
     FROM item_relationship_types irt 
     JOIN items it1 ON it1.itemtype = irt.type1 
     JOIN items it2 ON it2.itemtype = irt.type2 
     WHERE (it1.id = NEW.itemid1 AND it2.id = NEW.itemid2) 
     -- OR (it1.id = NEW.itemid2 AND it2.id = NEW.itemid1) 
     ) THEN RETURN NEW; 
ELSE 
     RAISE EXCEPTION 'type lookup failure'; 
     RETURN NULL; 
END IF; 

END; 
$func$ LANGUAGE 'plpgsql' 
     ; 

CREATE CONSTRAINT TRIGGER item_check_types 
     AFTER UPDATE OR INSERT 
     -- BEFORE UPDATE OR INSERT 
     ON item_relationships 
     FOR EACH ROW 
     EXECUTE PROCEDURE item_check_types() 
     ; 

INSERT INTO item_types(id) 
SELECT generate_series(1,10); 

INSERT INTO item_relationship_types (type1, type2) VALUES 
(1,3), (2,4), (3,5), (4,6); 

INSERT INTO items(id, itemtype) 
SELECT gs, gs % 10 
FROM generate_series(101,109) gs; 

INSERT INTO item_relationships(itemid1, itemid2) 
    VALUES (101,103), (102,104); -- Okay 
INSERT INTO item_relationships(itemid1, itemid2) 
    VALUES (101,104), (102,103); -- should fail 
+0

如果有人從'item_relationship_types'刪除一條記錄而不從'item_relationships'刪除相應的行?誠信將被打破。 – krokodilko

+0

是的,你也需要處理這種情況。 (不幸的是,級聯是不可能的) – wildplasser

+0

@ D4rt我回滾到原來的版本。它似乎是圓滿的。 – wildplasser