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();
我看不出沒有觸發器的事情,我不確定用觸發器做這件事是否值得。當依賴關係變長並且分支很多時,這可能變得非常昂貴。順便說一句,你真的需要N到M的關係,1到N是不夠的? – Eelke 2012-07-28 06:20:40
我想到了一些,並有一個想法,當你限制鏈接被允許改變的方式時,它可能會變得更容易。首先不允許更新events_dep表。其次只允許插入鏈接給沒有孩子的孩子。這是相對便宜的檢查,並會阻止創建循環鏈接,但會限制您的數據可以改變的方式。 – Eelke 2012-07-28 06:39:51
N到M是我需要的,因爲在我的情況下,每個節點都可以被許多其他節點所依賴,並且可以依賴於許多其他節點。我所瞄準的一個很好的例子就是卡恩學院的課程進度,在這裏建立了某種樹形結構來定義課程的依賴關係。 – Adam 2012-07-29 01:27:04