【问题标题】:Multiple Foreign Keys for the same business rule同一业务规则的多个外键
【发布时间】:2018-12-15 23:09:31
【问题描述】:

让我们跳到一个例子,说明一个表引用多个表:

CREATE TABLE Corses
(
   ID int PRIMARY KEY,
   .....
)  

CREATE TABLE Questions
(
    ID int PRIMARY KEY,
    .....
)

CREATE TABLE Answers
(
    ID int PRIMARY KEY,
    .....
)

CREATE TABLE Files
(
    ID INT PRIMARY KEY,

    Corse_ID INT,
    Question_ID INT,
    Answer_ID INT,

    FOREIGN KEY (Corse_ID) REFERENCES Corses(ID),
    FOREIGN KEY (Question_ID) REFERENCES Questions(ID),
    FOREIGN KEY (Answer_ID) REFERENCES Answers(ID)
)

上面的例子说明了一个学习应用程序中对其他对象(corses,问题和答案)的文件属性,所有对象的业务规则都是相同的,如下所示:

  • 文件必须附加到单个对象并且只能附加到单个对象
  • 一个对象可以没有或有很多文件附加到它上面 这使其成为一对多关系,并如上所示。

我的问题:

1。 当业务规则为 1-Many 时,这会使文件出现的其他外键列过时,例如,如果文件附加到屏幕截图之类的问题,则它仅附加到该问题,而不附加到答案而不是附加到死神。 每次出现实际上只使用一个外键。 必须有更好的方法来模拟这种情况。 还有其他方法可以实现更好的设计吗?

2。 当添加基于相同业务规则的多个 1-Many 关系并且子表必须依赖于父表中的行(文件必须附加到对象)时,我无法添加“NOT NULL”约束来强制执行此操作规则,因为我不知道我的文件将附加到哪个对象。 如何实现?

【问题讨论】:

  • 您的业务规则在没有上下文的情况下意义不大。没有人知道您的规则中的“对象”指的是什么。并且实体“文件”没有似乎与其他实体相关的名称 - 这增加了混乱。但是,您的 DDL 似乎非常不完整。定义主键(可能是合成键)是不够的。一个特定的问题通常有一个特定的答案——通常超过 1 个用于测验目的。然而,这种关系没有被捕捉到。我认为您在尝试定义“文件”之前还有更多工作要做。
  • 我的业务规则是能够将文件附加到对象,其中对象指的是尸体、问题或答案。对于理解我的问题,DDL 并不完整(DDL 的其余部分无助于更好地理解我的问题)。

标签: sql-server database-design foreign-keys relational-database entity-relationship


【解决方案1】:

这个问题可能有多个答案,但我在#4 下面的答案是我认为这种多态关联的更好解决方案。

首先要避免其他可能的选择:

  1. 基于基数的设计: 由于对象(Corse、问题或答案)和文件之间的关系是一对多,这意味着文件将托管引用对象表的 PK 的 FK。 这种设计存在以下问题:

    • 每个文件出现仅使用一个 FK,其余的已过时。
    • NOT NULL 约束不能使用,必须用 CHECK 约束替换,以检查是否填充了至少一个 FK。
  2. 基表设计: 创建具有 ID 列的基本抽象对象表,并在引用抽象对象表 ID 的所有对象表(Corse、问题和答案)中添加 FK,并最终在文件表中引用抽象对象表的 ID。 此设计存在以下问题:

    • 当创建一个对象时,它意味着代表一个 Corse、一个问题或一个答案(一个对象和一个对象)但使用这种设计我可以创建一个假设为问题的 Objet 并使用相同的对象代表科西嘉。必须使用带有函数的 触发器CHECK 约束来避免这种情况。
  3. 基于对象类型的设计: 创建一个 ID 列为 PK 的对象类型表并在 File 表中引用它,然后在没有 FK 的文件表中创建一个 Object_ID 列,最后在 ObjectType_ID 和 Object_ID 上添加 UNIQUE 约束列。 这种设计存在以下问题:

    • 文件可以附加到甚至不存在的(Corses、问题或答案)。必须使用带有函数的 触发器CHECK 约束来避免这种情况。
  4. 基于多对多的设计: 尽管关系是对象(Corse、问题或答案)和文件之间的 1-Many 关系,但它等效于通过修改关系表的 PK 的 Many-Many 关系。 首先,我在每个对象的文件和对象之间创建一个关系表,然后我使用 File_ID 列作为 PK。 这是 File-Corse 关系的 DDL,问题和答案也是如此:


CREATE TABLE Files
(
    ID INT PRIMARY KEY,
    .....
)

CREATE TABLE Corses
(
   ID INT PRIMARY KEY,
   .....
)

CREATE TABLE Files_Corses
(
    File_ID INT PRIMARY KEY,
    Corse_ID INT NOT NULL,

    FOREIGN KEY (File_ID) REFERENCES Files(ID),
    FOREIGN KEY (Corse_ID) REFERENCES Corses(ID)
)

【讨论】:

    【解决方案2】:

    这里有一种没有这些问题的替代设计:

    CREATE TABLE Objects
    (
        Id int PRIMARY KEY
    );
    
    CREATE TABLE Courses
    (
       CourseId int PRIMARY KEY,
       CONSTRAINT FK_Courses_Objects FOREIGN KEY (CourseId) REFERENCES Objects(Id)
    )  
    
    CREATE TABLE Questions
    (
        QuestionId int PRIMARY KEY,
        CONSTRAINT FK_Questions_Objects FOREIGN KEY (QuestionId) REFERENCES Objects(Id)
    
    )
    
    CREATE TABLE Answers
    (
        AnswerId int PRIMARY KEY,
        CONSTRAINT FK_Answers_Objects FOREIGN KEY (AnswerId) REFERENCES Objects(Id)
    
    )
    
    CREATE TABLE Files
    (
        FileId int PRIMARY KEY,
        ObjectId int NOT NULL CONSTRAINT FK_Files_Objects REFERENCES Objects(Id)
    )
    

    但是,您可以解决保留原始设计的第二个问题:

    CREATE TABLE Files
    (
        FileId int PRIMARY KEY,
    
        CourseId int REFERENCES Courses(CourseId),
        QuestionId int REFERENCES Questions(QuestionId),
        AnswerId int REFERENCES Answers(AnswerId),
    
        CONSTRAINT CHK_JustOneObjectReferenced  CHECK (
            CourseId IS NOT NULL AND QuestionId IS NULL AND AnswerId IS NULL
            OR CourseId IS NULL AND QuestionId IS NOT NULL AND AnswerId IS NULL
            OR CourseId IS NULL AND QuestionId IS NULL AND AnswerId IS NOT NULL
        )
    )
    

    【讨论】:

    • 您需要在 CHECK 约束中加上括号,以保证 AND 和 OR 的顺序正确。
    • @Brian。这里不需要括号。 AND 优先于 OR
    • 好的 - 我会改写。每当我在逻辑子句中混合 AND 和 OR 时,我总是包含括号以确保按我需要的顺序计算元素。
    • 这绝对是一个更好的设计,但我仍然可以看到这种方法的不好的一面。当一个对象被创建时,它意味着代表一个问题、一个问题或一个答案(一个对象和一个对象)但是使用你建议的设计我可以创建一个假设是一个问题的对象并使用同一个对象来代表一个 corse(corse 我只在 DB 上下文中谈论你的答案)。
    • @Brian,没关系,您可以随意包含不必要的括号以明确表达,但我是一个非常懒惰的程序员,喜欢节省击键。
    猜你喜欢
    • 2018-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-27
    相关资源
    最近更新 更多