Database design 在一列中使用来自多个表的ID

Database design 在一列中使用来自多个表的ID,database-design,relational-database,normalization,Database Design,Relational Database,Normalization,我的一位同事创建了一个类似于以下内容的模式。这是一个简化的模式,仅包括解决此问题所需的部分 系统规则如下: 部门可以有0到多个部门 一个部门必须只属于一个部门 文章可以分配给某个部门或该部门的某个部门 模式是: Department ---------- DepartmentID (PK) int NOT NULL DepartmentName varchar(50) NOT NULL Division -------- DivisionID (PK) int NOT NULL Depart

我的一位同事创建了一个类似于以下内容的模式。这是一个简化的模式,仅包括解决此问题所需的部分

系统规则如下:

  • 部门可以有0到多个部门
  • 一个部门必须只属于一个部门
  • 文章可以分配给某个部门或该部门的某个部门
  • 模式是:

    Department
    ---------- 
    DepartmentID (PK) int NOT NULL
    DepartmentName varchar(50) NOT NULL
    
    Division
    --------
    DivisionID (PK) int NOT NULL
    DepartmentID (FK) int NOT NULL
    DivisonName varchar(50) NOT NULL
    
    Article
    -------
    ArticleID (PK) int NOT NULL
    UniqueID int NOT NULL
    ArticleName varchar(50) NOT NULL
    
    他使用一个虚构的规则(因为没有更好的术语)定义了这个模式,即所有departmentID都在1到100之间,所有divisionid都在101到200之间。他说,当查询文章表时,您将知道UniqueID是来自Department表还是来自Division表,这取决于它属于哪个范围

    我认为这是一个糟糕的设计,并提出了以下备选方案:

    Department
    ----------
    DepartmentID (PK) int NOT NULL
    ParentDepartmentID (FK) int NULL /* Self-referencing foreign key.  Divisions have parent departments. */
    DepartmentName varchar(50) NOT NULL
    
    Article
    -------
    ArticleID (PK) int NOT NULL
    DepartmentID (FK) int NOT NULL
    ArticleName varchar(50) NOT NULL
    
    我相信这是一个适当规范化的模式,并适当地强制关系和数据完整性,同时遵守上面概述的业务规则

    我的具体问题是:

    我知道使用一列包含来自两个域的值是糟糕的设计,我可以在文章表中论证外键的好处。但是,是否有人能提供一篇特定数据库设计文章/论文的参考,我可以用它来备份我的立场。如果我能指出一些具体的东西,它会让事情变得更简单。1NF

    每一行和列的交点正好包含一个 适用域(无其他内容)

    解决问题的最简单方法是为每个部门引入“默认”部门,即“整个部门”。之后,只需将所有文章链接到部门。 可能是这样的(
    DepartmentDivisionNo=0
    表示整个部门):


    您的同事实施了一种称为多态关联的设计。也就是说,“外键”指的是两个不同的父表之一。大多数人会添加另一列
    parent\u type
    或类似的内容,这样您就可以知道给定行引用了哪个父表。以你的同事为例,他将id的范围细分了。这是一个脆弱的设计,因为您无法在数据库级别实施它。如果您插入的部门编号大于100,则无法知道您的文章是否适用于某个部门或部门

    然而,您已经开发了一种类似这样的设计:在一个表中存储多个相关类型,因此主键确保保持唯一,并且文章可以引用任何相关类型的任何实例

    还有另一种选择:

    想想面向对象的设计。如果希望允许两个不同的类具有项目,可以为这两个类创建一个公共超类或公共接口。您可以在SQL中执行相同的操作:

    ArticleProducer
    ---------------
    ProducerID (PK) int NOT NULL
    
    Department
    ----------
    DepartmentID (PK) int NOT NULL, (FK)->ArticleProducer
    DepartmentName varchar(50) NOT NULL
    
    Division
    --------
    DivisionID (PK) int NOT NULL, (FK)->ArticleProducer
    DepartmentID (FK) int NOT NULL
    DivisonName varchar(50) NOT NULL
    
    Article
    -------
    ArticleID (PK) int NOT NULL, (FK)->ArticleProducer
    UniqueID int NOT NULL
    ArticleName varchar(50) NOT NULL
    
    因此,一篇文章必须由一个
    文章制作人制作。每个部门或部门都是一个商品生产者

    另见

    有关多态关联的更多信息,请参阅我的演示文稿或我的书


    埃尔文·斯穆特的重新评论:

    您是对的,尝试强制所有子类型表中不超过一行有点棘手。不幸的是,MySQL在任何存储引擎中都不支持检查约束。您可以通过查找表实现类似的功能:

    CREATE TABLE ArticleProducerTypes (ProducerType TINYINT UNSIGNED PRIMARY KEY);
    INSERT INTO ArticleProducerTypes VALUES (1), (2);
    
    CREATE TABLE ArticleProducer (
      ProducerID INT UNSIGNED NOT NULL PRIMARY KEY,
      ProducerType TINYINT UNSIGNED NOT NULL,
      UNIQUE KEY (ProducerID,ProducerType),
      FOREIGN KEY (ProducerType)
        REFERENCES ArticleProducerTypes(ProducerType)
    ) ENGINE=InnoDB;
    
    CREATE TABLE DepartmentProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
    INSERT INTO DepartmentProducerType VALUES (1);
    
    CREATE TABLE Department (
      DepartmentID INT UNSIGNED NOT NULL PRIMARY KEY,
      DepartmentName VARCHAR(50) NOT NULL,
      ProducerType TINYINT UNSIGNED NOT NULL,
      FOREIGN KEY (DepartmentID, ProducerType) 
        REFERENCES ArticleProducer(ProducerID, ProducerType),
      FOREIGN KEY (ProducerType)
        REFERENCES DepartmentProducerType(ProducerType) -- restricted to '1'
    ) ENGINE=InnODB;
    
    CREATE TABLE DivisionProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
    INSERT INTO DivisionProducerType VALUES (2);
    
    CREATE TABLE Division (
      DivisionID INT UNSIGNED NOT NULL PRIMARY KEY,
      ProducerType TINYINT UNSIGNED NOT NULL,
      DepartmentID INT UNSIGNED NOT NULL,
      FOREIGN KEY (DivisionID, ProducerType) 
        REFERENCES ArticleProducer(ProducerID, ProducerType),
      FOREIGN KEY (ProducerType)
        REFERENCES DivisionProducerType(ProducerType), -- restricted to '2'
      FOREIGN KEY (DepartmentID)
        REFERENCES Department(DepartmentID)  
    ) ENGINE=InnODB;
    
    CREATE TABLE Article (
      ArticleID INT UNSIGNED NOT NULL PRIMARY KEY,
      ArticleName VARCHAR(50) NOT NULL,
      FOREIGN KEY (ArticleID)
        REFERENCES ArticleProducer(ProducerID)
    );
    
    现在ArticleProducer中的每一行都可以被部门或部门引用,但不能同时被两者引用

    如果我们想添加一个新的生产者类型,我们将向ArticleProducerTypes查找表添加一行,并为新类型创建一对新表。例如:

    INSERT INTO ArticleProducerTypes VALUES (3);
    
    CREATE TABLE PartnerProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
    INSERT INTO PartnerProducerType VALUES (3);
    
    CREATE TABLE Partner (
      PartnerID INT UNSIGNED NOT NULL PRIMARY KEY,
      ProducerType TINYINT UNSIGNED NOT NULL,
      FOREIGN KEY (PartnerID, ProducerType) 
        REFERENCES ArticleProducer(ProducerID, ProducerType),
      FOREIGN KEY (ProducerType)
        REFERENCES PartnerProducerType(ProducerType) -- restricted to '3'
    ) ENGINE=InnODB;
    

    但是,我们仍然有可能,两者都不包含对ArticleProducer中给定行的引用;i、 e.我们不能创建强制在某个依赖表中创建行的约束。我没有解决方案。

    部门和部门应该存储在同一个表中。像这样:

    DepDiv
    ---------- 
    ID (PK) int NOT NULL
    Name varchar(50) NOT NULL
    Type int -- ex.: 1 for department, 2 for division, etc., incase you need to differentiate later
    
    它们是如此相似的元素——你应该对它们一视同仁

    在这之后,就不需要为id号设置扭曲的逻辑范围了。无论如何,这种方法太难扩展了

    祝你好运。

    re:他使用一个虚构的规则(因为没有更好的术语)定义了模式,即所有部门ID都在1到100之间,所有部门ID都在101到200之间

    如果他想这样做,他应该使用另一个字段,如isDepartment yes/no。然后他将有一个表,用于部门和部门,其中包含ID、名称和isDepartment,并且ID字段将是Article表中的FK

    这将解决重叠的部门和部门ID,但不会解决部门和部门之间的1对多关系。为了加强这种关系,您需要两个表


    您还可以在department和division表中引入AuthorID字段,该字段与Article具有FK关系。这可能是一个自动生成的字段。这是一种将除法表中的复合键标准化的方法。

    我实际上喜欢Damir的答案——它“重新思考”了这个问题,并为这个新问题提供了正确的答案。然而,部门和部门之间存在差异——可能每个部门都可以访问属于其部门的文章。拥有属于默认部门或整个部门部门的文章意味着有两种不同类型的部门。从现在起,您将进行如下查询

    select * from xxx x inner join division d where d.joinkey = x.joinkey and d.division != 0.
    
    相反,我称我的解决方案为“不要忽略关系”:

    现在,如何实施已经提出的一些约束?为了解决这个问题,您可以创建一个“文章箱”,其中每个文章都必须属于一个箱,并且部门和部门都有箱子

    但那已经进入了杂草丛中,你将不能再继续生长了
    Department
    ---------- 
    DepartmentID (PK) int NOT NULL
    DepartmentName varchar(50) NOT NULL
    
    Division
    --------
    DivisionID (PK) int NOT NULL
    DepartmentID (FK) int NOT NULL
    DivisonName varchar(50) NOT NULL
    
    Article
    -------
    ArticleID (PK) int NOT NULL
    ArticleName varchar(50) NOT NULL
    
    ArticleBelongsToDepartment
    --------------------------
    ArticleID (PK) (FK) int NOT NULL
    DepartmentID (FK) int NOT NULL
    
    ArticleBelongsToDivision
    --------------------------
    ArticleID (PK) (FK) int NOT NULL
    DivisionID (FK) int NOT NULL