6

我有一个表,称为 EVENTS,其中每一行可以依赖于表中的 0 个或多个其他行。我需要一种表示这种关系的方法,它还可以防止循环依赖(即一组事件返回到同一组中的一个事件)。

我目前有一个 EVENTS 外部的链接表,称之为 EVENTS_DEP。此表将相关行链接到它们所依赖的行,并允许对一行进行多个依赖。如何使用这样的表防止循环依赖?

注意:如果完全可以仅通过数据库设计(而不是脚本、触发器等)来执行此操作,那将是理想的。

此外,如果这只能使用触发器来完成,请告诉我应该在哪种触发器上运行(即在什么事件上)(在插入时,也许?)。

4

2 回答 2

8

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

它的作用是在父 A 和子 B 之间添加新链接时,它使用 A WITH 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();
于 2012-07-30T16:29:47.433 回答
1

在没有编程触发器的情况下,使用 SQL 引擎是不可能做到这一点的。对于支持这一点的 SQl 引擎,它需要支持递归 SQL(也称为递归 WITH 表达式,或递归 CTE),以及对断言的可靠支持。

许多支持 CTE 的/WITH 表达式,尽管可能并非所有的都支持该功能的递归版本。至于断言otoh,我被告知只有一个系统支持它们,但是实现是如此的缺陷和错误,以至于考虑认真使用它是可笑的。

有一些系统可以让你完全按照你的要求去做,但不要指望将 SQL 与这样的系统对话。这种系统的作者以保持他们的婴儿的关系为荣。

于 2012-07-28T17:54:46.503 回答