Предотвращение циклов в отношениях со многими ссылками (Postgres) - PullRequest
0 голосов
/ 05 марта 2020

Предположим, вы хотите иметь иерархию thing s, в которой thing может иметь несколько thing родителей и детей:

CREATE TABLE thing (
    id SERIAL PRIMARY KEY
);

CREATE TABLE thing_association (
    parent_id INTEGER NOT NULL,
    child_id INTEGER NOT NULL,
    PRIMARY KEY (parent_id, child_id),
    CHECK (parent_id != child_id),
    FOREIGN KEY (parent_id) REFERENCES thing(id),
    FOREIGN KEY (child_id) REFERENCES thing(id)
);

Ограничение CHECK предотвращает thing от наличие отношения с самим собой, а ограничение PRIMARY KEY предотвращает дублирование отношений, но можно ли предотвратить циклы?

Точнее, если строка (x, y) существует в таблице thing_association, может ли строка (y, x) быть запрещенным для вставки?

Пойдем дальше, если в thing_association существуют строки (x, y) и (y, z), можно ли предотвратить вставку строки (z, x)?

1 Ответ

0 голосов
/ 05 марта 2020

Я надеялся выполнить sh это без триггеров, но я не уверен, что это возможно. Я смог выполнить sh это с помощью BEFORE INSERT триггера:

CREATE TABLE thing (
    id SERIAL PRIMARY KEY
);

CREATE TABLE thing_association (
    parent_id INTEGER NOT NULL,
    child_id INTEGER NOT NULL,
    PRIMARY KEY (parent_id, child_id),
    CHECK (parent_id != child_id),
    FOREIGN KEY (parent_id) REFERENCES thing(id),
    FOREIGN KEY (child_id) REFERENCES thing(id)
);

/* maps every thing to all of it's parents */
CREATE VIEW thing_hierarchy AS
WITH RECURSIVE children AS (
    SELECT
        child_id,
        parent_id
    FROM thing_association
    UNION SELECT
        children.child_id,
        parents.parent_id
    FROM thing_association AS parents
    INNER JOIN children
        ON children.parent_id = parents.child_id
) SELECT * FROM children;

CREATE FUNCTION check_thing_association_loop() RETURNS TRIGGER AS $$
BEGIN
    IF ((NEW.parent_id, NEW.child_id) in (SELECT child_id, parent_id FROM thing_hierarchy)) THEN
    RAISE EXCEPTION 'Cannont create a hierarchy loop';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER thing_association_insert_check
    BEFORE INSERT ON thing_association
    FOR EACH ROW EXECUTE FUNCTION check_thing_association_loop();

Вы можете объединить представление в функцию триггера, но представление полезно само по себе, и оно сохраняет лаконичность.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...