2012-07-28 209 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与这样的系统交谈。这些系统的作者对保持婴儿关系感到骄傲。