2016-04-24 8 views
2

Ich habe die folgenden Tabellen zum Erstellen einer beliebigen Anzahl von Elementen mit unterschiedlichen Typen.Einschränkung zwischen zwei rekursiven Viele-zu-viele-Beziehungen

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 
); 

Die items Tabelle hat eine rekursive many-to-many-Beziehung item_relationship genannt.

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

Die item_types Tabelle hat eine rekursive many-to-many-Beziehung item_relationship_types genannt.

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) 
); 

Nun, was ich will, ist zu tun, irgendwie eine Einschränkung haben, dass Sie nicht versehentlich eine item_relationship, die ungültig ist erstellen können, das heißt, wo die item_types der Elemente sind in keiner item_relationship_type gefunden. Ich habe zwei Fragen.

  1. Ist eine solche Einschränkung sinnvoll? Ich denke, dass das Einfügen einer falschen Beziehung ein Fehler ist, der in der Geschäftslogik leicht passieren könnte, so dass sich die Beschränkung in der DB als wichtig erweist.

  2. Was ist der vernünftige Weg, die Beschränkung tatsächlich zu implementieren?

+0

Hinweis: Hier ist keine Rekursion beteiligt; Dies ist eine Art von * model-> instance * Vererbungsmuster. – wildplasser

+1

Was genau bedeutet "rekursiv"? Wenn zum Beispiel die Tabelle 'item_relationship_types' die folgenden Datensätze enthält:' (1,2), (2,3), (3,4) ', bedeutet dies, dass Typ 1 nicht nur mit Typ 2, sondern auch mit verwandt ist 3 und 4? – krokodilko

+0

@wildplasser Danke für die Klarstellung. – ieyasu

Antwort

1

Ein möglicher Ansatz zur Arbeit könnte item_relationship_types Tabelle mit einem Surrogat Primärschlüssel werden erstreckt:

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) 
); 

und th en einen Fremdschlüssel deutet auf das Ersatzschlüssel in item_relationships Tabelle hinzufügen:

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) 
); 

Sie müssen auch einen Trigger erstellen, die von der Eingabe type_rel_id Wert in item_relationships Tabelle verhindert, die nicht auf diese beiden Elemente im Zusammenhang mit den Typen zu den Einspeisepunkten in item_relationship_types Tabelle.

+0

Ich habe versucht, die Trigger-Variante, aber es erwies sich als zu langsam. Ich habe Ihre Idee erweitert und anstatt einen Ersatzschlüssel zu erstellen, habe ich 'type1' und' type2' von 'item_relationship_types' direkt zu' item_relationships' hinzugefügt. In 'item_relationships' habe ich' FOREIGN KEY (type1, itemid1) REFERENCES items (itemtype, id) 'und selbe für' type2' und 'itemid2' hinzugefügt. Dann habe ich 'FOREIGN KEY (type1, type2) REFERENCES item_relation_types (type1, type2)' hinzugefügt. Dies kümmert sich effizient um die Einschränkungen, aber etwas fühlt sich an, als ob man "type1" und "type2" zu "item_relations" hinzufügen müsste. Irgendwelche Gedanken zu dieser Lösung? – ieyasu

1
  • dies ist nicht perfekt, aber es scheint,

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

Was passiert, wenn jemand einen Datensatz aus 'item_relationship_types' löscht, ohne eine entsprechende Zeile aus' item_relationships' zu löschen? Die Integrität wird gebrochen. – krokodilko

+0

Ja, Sie müssen diesen Fall auch behandeln. (leider ist Kaskadierung hier nicht möglich) – wildplasser

+0

@ D4rt Ich habe auf die Originalversion zurückgerollt. Es scheint, als wäre es ein Fehler. – wildplasser