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