【问题标题】:Constraining between two recursive many-to-many relationships两个递归多对多关系之间的约束
【发布时间】:2016-08-17 20:36:41
【问题描述】:

我有下表用于创建任意数量的不同类型的项目。

CREATE TABLE item_types (
    id SERIAL,
    PRIMARY KEY (id)
    -- Other columns omitted
);

CREATE TABLE items (
    id SERIAL,
    itemtype integer NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (itemtype) REFERENCES item_types (id)
    -- Other columns omitted
);

items 表具有称为item_relationship 的递归多对多关系。

CREATE TABLE item_relationships (
    itemid1 integer,
    itemid2 integer,
    PRIMARY KEY (itemid1, itemid2),
    FOREIGN KEY (itemid1) REFERENCES items (id),
    FOREIGN KEY (itemid2) REFERENCES items (id) 
);

item_types 表具有称为item_relationship_types 的递归多对多关系。

CREATE TABLE item_relationship_types (
    type1 integer,
    type2 integer,
    PRIMARY KEY (type1, type2),
    FOREIGN KEY (type1) REFERENCES item_types (id),
    FOREIGN KEY (type2) REFERENCES item_types (id)  
);

现在,我想做的是以某种方式限制您不能意外创建无效的item_relationship,即在任何item_relationship_type 中都找不到项目的item_types。我有两个问题。

  1. 这样的约束有意义吗?我认为插入错误的关系是业务逻辑中很容易发生的错误,因此在 DB 中进行约束感觉很重要。

  2. 实际实施约束的明智方法是什么?

【问题讨论】:

  • 注意:这里不涉及递归;这是一种model->instance继承模式。
  • “递归”到底是什么意思?比如表item_relationship_types有如下记录:(1,2),(2,3),(3,4),是不是表示类型1不仅与类型2相关,还与3和4相关?
  • @wildplasser 感谢您的澄清。
  • @kordirko 在您的示例 1 中与 3 和 4 间接相关,但在 (1,3) 和 (1,4) 类型的项目之间创建 item_relationship 是不可能的。仅在您列出的那些之间。

标签: sql postgresql many-to-many data-modeling


【解决方案1】:
  • 这并不完美,但似乎可以工作

CREATE FUNCTION item_check_types( ) RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS (
        SELECT 1
        FROM item_relationship_types irt
        JOIN items it1 ON it1.itemtype = irt.type1
        JOIN items it2 ON it2.itemtype = irt.type2
        WHERE (it1.id = NEW.itemid1 AND it2.id = NEW.itemid2)
        -- OR (it1.id = NEW.itemid2 AND it2.id = NEW.itemid1)
        ) THEN RETURN NEW;
ELSE
        RAISE EXCEPTION 'type lookup failure';
        RETURN NULL;
END IF;

END;
$func$ LANGUAGE 'plpgsql'
        ;

CREATE CONSTRAINT TRIGGER item_check_types
        AFTER UPDATE OR INSERT
        -- BEFORE UPDATE OR INSERT
        ON item_relationships
        FOR EACH ROW
        EXECUTE PROCEDURE item_check_types()
        ;

INSERT INTO item_types(id)
SELECT generate_series(1,10);

INSERT INTO item_relationship_types (type1, type2) VALUES
(1,3), (2,4), (3,5), (4,6);

INSERT INTO items(id, itemtype)
SELECT gs, gs % 10
FROM generate_series(101,109) gs;

INSERT INTO item_relationships(itemid1, itemid2)
  VALUES (101,103), (102,104); -- Okay
INSERT INTO item_relationships(itemid1, itemid2)
  VALUES (101,104), (102,103); -- should fail

【讨论】:

  • 如果有人从item_relationship_types 中删除了一条记录而没有从item_relationships 中删除相应的行怎么办?完整性将被破坏。
  • 是的,您也需要处理这种情况。 (不幸的是,级联在这里是不可能的)
  • @D4rt 我回滚到原始版本。它似乎是正确的。
  • 您将items (id)item_types (id) 进行比较。 items (id) 中的值范围从 101 到 109,item_types (id) 中的值从 1 到 9,因此它们永远不会匹配。您的代码在两次插入时都失败了。
  • 这里第一对插入成功,第二对失败,正如预期的那样。我会再调查一下。
【解决方案2】:

一种可能的方法是使用代理主键扩展item_relationship_types 表:

CREATE TABLE item_relationship_types (
    id integer SERIAL,
    type1 integer,
    type2 integer,
    PRIMARY KEY (id),
    UNIQUE (type1, type2),
    FOREIGN KEY (type1) REFERENCES item_types (id),
    FOREIGN KEY (type2) REFERENCES item_types (id)  
);

然后将指向该代理键的外键添加到item_relationships 表中:

CREATE TABLE item_relationships (
    itemid1 integer,
    itemid2 integer,
    type_rel_id integer not null,
    PRIMARY KEY (itemid1, itemid2),
    FOREIGN KEY (itemid1) REFERENCES items (id),
    FOREIGN KEY (itemid2) REFERENCES items (id),
    FOREIGN KEY (type_rel_id) REFERENCES item_relationship_types (id)  
);

您还需要创建一个触发器,防止在item_relationships 表中输入type_rel_id 值,该值指向与item_relationship_types 表中与这两项无关的类型的条目。

【讨论】:

  • 我尝试了触发器变体,但它被证明太慢了。我扩展了您的想法,而不是创建代理键,而是将type1type2item_relationship_types 直接添加到item_relationships。在item_relationships 中,我添加了FOREIGN KEY (type1, itemid1) REFERENCES items (itemtype, id)type2itemid2。然后我添加了FOREIGN KEY (type1, type2) REFERENCES item_relation_types (type1, type2)。这可以有效地处理约束,但是对于必须将 type1type2 添加到 item_relations 感觉有些不对劲。对此解决方案有何想法?
猜你喜欢
  • 2018-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 1970-01-01
  • 1970-01-01
  • 2020-04-19
  • 2011-04-22
相关资源
最近更新 更多