互斥的多对多关系

Mad*_*ist 9 postgresql database-design constraint many-to-many

我有一个containers可以与多个表建立多对多关系的表,假设它们是plantsanimalsbacteria。每个容器可以包含任意数量的植物、动物或细菌,并且每个植物、动物或细菌可以在任意数量的容器中。

到目前为止,这非常简单,但我遇到的问题是每个容器应该只包含相同类型的元素。例如,同时包含植物和动物的混合容器应该是数据库中的约束违规。

我的原始架构如下:

containers
----------
id
...
...


containers_plants
-----------------
container_id
plant_id


containers_animals
------------------
container_id
animal_id


containers_bacteria
-------------------
container_id
bacterium_id
Run Code Online (Sandbox Code Playgroud)

但是对于这个模式,我无法想出如何实现容器应该是同构的约束。

有没有办法通过参照完整性来实现这一点,并在数据库级别确保容器是同类的?

我为此使用 Postgres 9.6。

And*_*y M 10

如果您同意为其引入一些冗余,则有一种方法可以仅以声明方式实现这一点,而无需对当前设置进行太多更改。下面的内容可以被认为是对RDFozz 建议的发展,尽管在我阅读他的答案之前这个想法已经在我的脑海中完全形成(而且它的不同足以保证有自己的答案帖子)。

执行

这是你要做的,一步一步:

  1. containerTypes按照 RDFozz 的回答中建议的方式创建一个表格:

    CREATE TABLE containerTypes
    (
      id int PRIMARY KEY,
      description varchar(30)
    );
    
    Run Code Online (Sandbox Code Playgroud)

    使用每种类型的预定义 ID 填充它。出于此答案的目的,让它们匹配 RDFozz 的示例:1 代表植物,2 代表动物,3 代表细菌。

  2. 添加一containerType_idcontainers并使其不可为空和外键。

    ALTER TABLE containers
    ADD containerType_id int NOT NULL
      REFERENCES containerTypes (id);
    
    Run Code Online (Sandbox Code Playgroud)
  3. 假设该id列已经是 的主键containers,则在 上创建唯一约束(id, containerType_id)

    ALTER TABLE containers
    ADD CONSTRAINT UQ_containers_id_containerTypeId
      UNIQUE (id, containerType_id);
    
    Run Code Online (Sandbox Code Playgroud)

    这就是冗余开始的地方。如果id声明为主键,我们可以放心它是唯一的。如果它是唯一的,那么id和 另一列的任何组合也必然是唯一的,而无需额外声明唯一性——那么,有什么意义呢?关键是通过正式声明列对唯一我们让它们是可引用的,即成为外键约束的目标,这就是这部分的内容。

  4. containerType_id向每个联结表 ( containers_animals, containers_plants, containers_bacteria)添加一列。使其成为外键是完全可选的。关键是确保列对所有行具有相同的值,每个表的值不同:1 代表containers_plants、2 代表containers_animals、3 代表containers_bacteria,根据 中的描述containerTypes。在每种情况下,您还可以将该值设为默认值以简化插入语句:

    ALTER TABLE containers_plants
    ADD containerType_id NOT NULL
      DEFAULT (1)
      CHECK (containerType_id = 1);
    
    ALTER TABLE containers_animals
    ADD containerType_id NOT NULL
      DEFAULT (2)
      CHECK (containerType_id = 2);
    
    ALTER TABLE containers_bacteria
    ADD containerType_id NOT NULL
      DEFAULT (3)
      CHECK (containerType_id = 3);
    
    Run Code Online (Sandbox Code Playgroud)
  5. 在每个联结表中,使这对列(container_id, containerType_id)成为引用 的外键约束containers

    ALTER TABLE containers_plants
    ADD CONSTRAINT FK_containersPlants_containers
      FOREIGN KEY (container_id, containerType_id)
      REFERENCES containers (id, containerType_id);
    
    ALTER TABLE containers_animals
    ADD CONSTRAINT FK_containersAnimals_containers
      FOREIGN KEY (container_id, containerType_id)
      REFERENCES containers (id, containerType_id);
    
    ALTER TABLE containers_bacteria
    ADD CONSTRAINT FK_containersBacteria_containers
      FOREIGN KEY (container_id, containerType_id)
      REFERENCES containers (id, containerType_id);
    
    Run Code Online (Sandbox Code Playgroud)

    如果container_id已定义为对 的引用containers,请随意从每个表中删除不再需要的约束。

这个怎么运作

通过添加容器类型列并使其参与外键约束,您准备了一种防止容器类型更改的机制。containers仅当使用DEFERRABLE子句定义外键时,才可能更改类型中的类型,它们不应该出现在此实现中。

即使它们是可延迟的,由于 -containers连接表关系另一侧的检查约束,更改类型仍然是不可能的。每个连接表只允许一种特定的容器类型。这不仅可以防止现有引用更改类型,还可以防止添加错误的类型引用。也就是说,如果您有一个类型 2(动物)的容器,则只能使用允许类型 2 的表向其中添加项目,即containers_animals,并且无法添加引用它的行,例如,containers_bacteria接受仅键入 3 个容器。

最后,你自己的决定,为不同的表plantsanimals以及bacteria和不同结点的表为每个实体类型,已经使它不可能的容器有多于一个类型的项目。

因此,所有这些因素结合在一起,以纯粹的声明方式确保您的所有容器都是同质的。


Eva*_*oll 0

我有一个表容器,可以与多个表建立多对多关系,假设这些是植物、动物和细菌。

这是一个坏主意。

但对于这个模式,我无法想出如何实现容器应该是同构的约束。

现在你知道为什么了。=)

我相信您一直坚持从面向对象编程(OO)继承的想法。OO 继承解决了代码重用的问题。在 SQL 中,冗余代码是我们遇到的问题中最少的。诚信是第一位的。性能往往是第二位的。我们会享受前两个的痛苦。我们没有可以消除成本的“编译时”。

因此,放弃对代码重用的痴迷吧。现实世界中,植物、动物和细菌的容器在每个地方都有根本的不同。“容纳东西”的代码重用组件不会为您做这件事。把他们分开。它不仅会给你带来更多的完整性和更高的性能,而且在未来你会发现更容易扩展你的模式:毕竟,在你的模式中你已经必须分解所包含的项目(植物,动物等) ,似乎至少有可能你必须拆开容器。那么您就不会想要重新设计整个架构。