2012-07-28 210 views
4

我有一個表,稱之爲事件,其中每行可以依賴於表中的0個或更多其他行。我需要一種表示這種關係的方式,這種方式也可以防止循環依賴(即一組事件引回同一組中的事件)。PostgreSQL設計依賴​​關係樹沒有循環依賴關係

我目前在EVENTS外部有一個鏈接表,稱之爲EVENTS_DEP。這個表格將依賴的行鏈接到它們依賴的行,並允許在一行上存在多個依賴關係。我將如何防止使用這種表的循環依賴關係?

注意:如果完全可以僅通過數據庫設計(而不是腳本,觸發器等)完成此操作,則這將是理想的。另外,如果這隻能使用觸發器完成,請讓我知道它應該在什麼樣的觸發器上運行(插入時,也許?)。

+2

我看不出沒有觸發器的事情,我不確定用觸發器做這件事是否值得。當依賴關係變長並且分支很多時,這可能變得非常昂貴。順便說一句,你真的需要N到M的關係,1到N是不夠的? – Eelke 2012-07-28 06:20:40

+1

我想到了一些,並有一個想法,當你限制鏈接被允許改變的方式時,它可能會變得更容易。首先不允許更新events_dep表。其次只允許插入鏈接給沒有孩子的孩子。這是相對便宜的檢查,並會阻止創建循環鏈接,但會限制您的數據可以改變的方式。 – Eelke 2012-07-28 06:39:51

+0

N到M是我需要的,因爲在我的情況下,每個節點都可以被許多其他節點所依賴,並且可以依賴於許多其他節點。我所瞄準的一個很好的例子就是卡恩學院的課程進度,在這裏建立了某種樹形結構來定義課程的依賴關係。 – Adam 2012-07-29 01:27:04

回答

4

INSERT觸發器來檢查這一點。

假設下面的表結構

CREATE TABLE event (
    id bigserial PRIMARY KEY, 
    foo varchar 
); 

CREATE TABLE event_deps (
    parent bigint REFERENCES event(id), 
    child bigint REFERENCES event(id), 

    PRIMARY KEY (parent, child), 
    CHECK (parent <> child) 
); 

將需要以下INSERT觸發器

CREATE FUNCTION deps_insert_trigger_func() RETURNS trigger AS $BODY$ 
    DECLARE 
     results bigint; 
    BEGIN 
     WITH RECURSIVE p(id) AS (
      SELECT parent 
       FROM event_deps 
       WHERE child=NEW.parent 
      UNION 
      SELECT parent 
       FROM p, event_deps d 
       WHERE p.id = d.child 
      ) 
     SELECT * INTO results 
     FROM p 
     WHERE id=NEW.child; 

     IF FOUND THEN 
      RAISE EXCEPTION 'Circular dependencies are not allowed.'; 
     END IF; 
     RETURN NEW; 
    END; 
$BODY$ LANGUAGE plpgsql; 

CREATE TRIGGER before_insert_event_deps_trg BEFORE INSERT ON event_deps 
    FOR EACH ROW 
    EXECUTE PROCEDURE deps_insert_trigger_func(); 

它能做什麼,當父和子B之間添加一個新的鏈接,它使用一臺帶是RECURSIVE查詢查找A. B的所有祖先不應該是其中之一。

UPDATE觸發器比較難,因爲觸發器執行到舊鏈接時仍然存在,所以INSERT觸發器的測試可能在用於UPDATE時產生誤報。

因此,對於UPDATE,我們需要添加一些額外的條件來隱藏舊數據。

CREATE FUNCTION deps_update_trigger_func() RETURNS trigger AS $BODY$ 
    DECLARE 
     results bigint; 
    BEGIN 
     WITH RECURSIVE p(id) AS (
      SELECT parent 
       FROM event_deps 
       WHERE child=NEW.parent 
        AND NOT (child = OLD.child AND parent = OLD.parent) -- hide old row 
      UNION 
      SELECT parent 
       FROM p, event_deps d 
       WHERE p.id = d.child 
        AND NOT (child = OLD.child AND parent = OLD.parent) -- hide old row 
      ) 
     SELECT * INTO results 
     FROM p 
     WHERE id=NEW.child; 

     IF FOUND THEN 
      RAISE EXCEPTION 'Circular dependencies are not allowed.'; 
     END IF; 
     RETURN NEW; 
    END; 
$BODY$ LANGUAGE plpgsql; 

CREATE TRIGGER before_update_event_deps_trg BEFORE UPDATE ON event_deps 
    FOR EACH ROW 
    EXECUTE PROCEDURE deps_update_trigger_func(); 
+1

優秀的答案,謝謝一百萬的幫助。我需要開始將這些東西插入到我的數據庫中(我有大約15個地方,這些地方的變化可以爲我提供很大的幫助)。 – Adam 2012-08-01 18:50:50

1

這是不可能的與SQL引擎和沒有編程觸發器。爲了支持SQl引擎,它需要支持遞歸SQL(也就是遞歸的WITH表達式或遞歸的CTE),以及對ASSERTION的可靠支持。

許多人都支持CTE的/ WITH表達式,但也許並非所有人都支持遞歸版本的功能。至於說明otoh,我被告知只有一個系統支持它們,但是執行過程很有缺陷並且有bug纏繞,因此認真考慮使用它是很可笑的。

有些系統可以讓你按照你的要求完成,但是不要指望SQL與這樣的系統交談。這些系統的作者對保持嬰兒關係感到驕傲。