【问题标题】:Maintaining subclass integrity in a relational database在关系数据库中维护子类的完整性
【发布时间】:2010-09-24 20:37:58
【问题描述】:

假设我有一个代表超级班级students的表格。然后我有 N 个表来表示该对象的子类(athletesmusicians 等)。如何表达一个约束,使得学生必须在一个(不多也不少)子类中建模?

关于 cmets 的说明:

  • 这是手动维护的,而不是通过 ORM 包。
  • 与此相关的项目位于 SQL Server 之上(但很高兴看到通用解决方案)
  • 这可能不是最好的例子。关于子类化,我们可以考虑几种情况,而我恰好发明了这个学生/运动员的例子。

A) 在真正的面向对象的方式中,超类可以单独存在而无需在任何子类中建模。

B) 在现实生活中,任何对象或学生都可以有多个角色。

C) 我试图说明的特定场景是要求每个对象都在一个子类中实现。将超类视为抽象实现,或者只是从其他不同的对象类/实例中分解出来的共性。

感谢大家的意见,尤其是比尔。

【问题讨论】:

  • 只是为了澄清一下:您是手动管理这个还是使用像休眠这样的 ORM 解决方案?
  • 您使用的是什么数据库?如果您使用的是 PostgreSQL,它可以使用表继承。
  • 我很好奇,为什么学生不能既是运动员又是护士?
  • @Elijah - 真的吗?必须检查一下 - 谢谢! @Charles - 好点 - 这看起来确实是个坏例子
  • 提防特定于 DB 的功能(例如从 Postgres 继承的表)。最好保留每个 RDBMS 中的基本关系特性。可移植性和代码可读性是加分项。

标签: sql sql-server database-design oop


【解决方案1】:

每个学生记录将有一个子类列(为了论证,假设它是一个 CHAR(1))。 {A = 运动员,M = 音乐家...}

现在创建您的运动员和音乐家表。它们还应该有一个 SubClass 列,但应该有一个检查约束硬编码它们所代表的表类型的值。例如,您应该为 Athlete 表的 SubClass 列设置默认值“A”和 CHECK 约束“A”。

使用 StudentID AND Subclass 的 COMPOSITE 外键将您的 Musician 和 Athlete 表链接到 Student 表。你完成了!去享受一杯好喝的咖啡吧。

CREATE TABLE Student (
    StudentID INT NOT NULL IDENTITY PRIMARY KEY,
    SubClass CHAR(1) NOT NULL,
    Name VARCHAR(200) NOT NULL,
    CONSTRAINT UQ_Student UNIQUE (StudentID, SubClass)
);

CREATE TABLE Athlete (
    StudentID INT NOT NULL PRIMARY KEY,
    SubClass CHAR(1) NOT NULL,
    Sport VARCHAR(200) NOT NULL,
    CONSTRAINT CHK_Jock CHECK (SubClass = 'A'),
    CONSTRAINT FK_Student_Athlete FOREIGN KEY (StudentID, Subclass) REFERENCES Student(StudentID, Subclass)
);

CREATE TABLE Musician (
    StudentID INT NOT NULL PRIMARY KEY,
    SubClass CHAR(1) NOT NULL,
    Instrument VARCHAR(200) NOT NULL,
    CONSTRAINT CHK_Band_Nerd CHECK (SubClass = 'M'),
    CONSTRAINT FK_Student_Musician FOREIGN KEY (StudentID, Subclass) REFERENCES Student(StudentID, Subclass)
);

【讨论】:

    【解决方案2】:

    这里有几种可能性。一个是每个表中的CHECKstudent_id 没有出现在任何其他姐妹子类型表中。这可能很昂贵,并且每次您需要新的子类型时,都需要修改所有现有表中的约束。

    CREATE TABLE athletes (
      student_id INT NOT NULL PRIMARY KEY,
      FOREIGN KEY (student_id) REFERENCES students(student_id),
      CHECK (student_id NOT IN (SELECT student_id FROM musicians 
                          UNION SELECT student_id FROM slackers 
                          UNION ...)) 
    );
    

    编辑: @JackPDouglas 正确指出 Microsoft SQL Server 不支持上述形式的 CHECK 约束。事实上,根据 SQL-99 标准,引用另一个表也无效(请参阅http://kb.askmonty.org/v/constraint_type-check-constraint)。

    SQL-99 为多表约束定义了一个元数据对象。这称为 ASSERTION,但我不知道任何实现断言的 RDBMS。

    可能更好的方法是将students表中的主键设为复合主键,第二列表示子类型。然后将每个子表中的该列限制为与表所表示的子类型相对应的单个值。 编辑:无需将 PK 设为子表中的复合键。

    CREATE TABLE athletes (
      student_id INT NOT NULL PRIMARY KEY,
      student_type CHAR(4) NOT NULL CHECK (student_type = 'ATHL'),
      FOREIGN KEY (student_id, student_type) REFERENCES students(student_id, student_type)
    );
    

    当然,student_type 也可以很容易地成为一个整数,我只是将其显示为一个字符以用于说明目的。

    如果您不支持CHECK 约束(例如 MySQL),那么您可以在触发器中执行类似的操作。

    我阅读了您关于确保在 some 子类表中为超类表中的每一行都存在一行的后续内容。我认为没有一种实用的方法可以使用 SQL 元数据和约束来做到这一点。我可以建议满足此要求的唯一选择是使用Single-Table Inheritance。否则,您需要依赖应用程序代码来执行它。

    编辑: JackPDouglas 还建议使用基于Class Table Inheritance 的设计。请参阅his example 或我的类似技术示例hereherehere

    【讨论】:

    • -1 获取有关检查约束的误导性信息。来自SQL Server 2008R2 docs:“搜索条件必须计算为布尔表达式,并且不能引用另一个表。”
    • 我后来推荐了一些不同的东西而没有功劳?好吧,无论如何感谢您解释您的反对意见,大多数人不会这样做。
    • 如果您的初始解决方案已经奏效,那将是一个有吸引力的选择。我认为杰克最初被您的第一个解决方案所吸引,只是因为它似乎不受支持而浪费了他的时间而感到失望。 +1 编辑您的答案:-)
    • IMO 误导性答案胜过好答案。没什么私人的,我只是想让 SO 用户获得好的信息 - 我已经删除了反对票,因为你已经编辑了你的答案。我什至会投票,除非我认为您的最后一段不正确(example
    • 感谢您删除反对票。我阅读了您的答案并投票赞成。我还提供了几个链接,这些链接指向我给出的关于同一解决方案的答案。
    【解决方案3】:

    如果您对数据建模感兴趣,除了对象建模,建议您在网上查找“关系建模泛化专业化”。

    曾经有一些很好的资源可以很好地解释这种模式。

    我希望那些资源还在。

    这是我希望您能找到的简化视图。

    在开始设计数据库之前,想出一个概念数据模型将数据库中存储的值与主题相关联是很有用的。制作概念数据模型实际上是数据分析,而不是数据库设计。有时很难将分析和设计分开。

    在概念级别对数据进行建模的一种方法是实体关系 (ER) 模型。有一些众所周知的模式可以对专业化-泛化情况进行建模。将这些 ER 模式转换为 SQL 表(称为逻辑设计)非常简单,尽管您必须做出一些设计选择。

    如果我没看错的话,您给出的学生可能担任多个角色(例如音乐家)的案例可能无法说明您感兴趣的案例。您似乎对子类互斥的情况感兴趣。也许车辆可能是汽车、卡车或摩托车的情况可能更容易讨论。

    您可能会遇到的一个区别是超类的通用表实际上并不需要类型代码列。单个超类实例的类型可以通过各种子类表中是否存在外键来派生。包含或省略类型代码是否更明智取决于您打算如何使用数据。

    【讨论】:

      【解决方案4】:

      有趣的问题。当然,子表有 FK 约束,所以必须有一个学生来处理这些子表。

      主要问题是在插入时尝试检查。必须首先插入学生,这样您就不会违反子表中的 FK 约束,因此执行检查的触发器将不起作用。

      如果您真的担心这一点,您可以编写一个不时检查的应用程序。我认为最大的恐惧是删除。有人可以删除子表条目,但不能删除学生。您可以使用触发器来检查何时从子表中删除项目,因为这可能是最大的问题。

      我有一个数据库,每个子类层次结构都有一个表,就像这样。我使用 Hibernate 及其正确映射,因此它会自动删除所有内容。如果通过“手动”执行此操作,那么我将确保始终使用适当的级联删除父级呵呵 :)

      【讨论】:

        【解决方案5】:

        谢谢,比尔。你让我思考...

        超类表有一个子类代码列。每个子类表都有一个外键约束,以及一个指示 id 与超类表的子集(其中代码 = 运动员)存在的约束。

        这里唯一缺少的部分是可以在没有子类的情况下对超类进行建模。即使您将代码列设为强制性,它也可能只是一个空连接。这可以通过添加一个约束来解决,即超类的 id 存在于子类表中​​的 id 的联合中。如果在事务中间强制执行约束,则插入会因为这两个约束而变得有点麻烦。或者只是不担心未分类的对象。

        编辑:Bleh,听起来不错……但是由于不支持引用其他表的子查询这一事实而受到阻碍。至少在 SQL Server 中没有。

        【讨论】:

          【解决方案6】:

          这可以通过添加一个约束来解决,即超类的 id 存在于 子类表中的 id。

          根据您想在架构中放入多少智能(以及 MS SQL Server 允许您在其中放入多少),您实际上不需要合并子类表,因为您知道,如果id 存在于任何子类表中,它必须与子类代码列标识的子类存在于同一子类中。

          【讨论】:

          • (a) 您不能在 MS SQL Server 的约束中查询另一个表,并且 (b) 您不能查询名称取决于数据值的表。
          【解决方案7】:

          我可能会添加一个检查约束。
          将 ForeignKeys 创建为 Nullable。 添加一个 Check 以确保它们不都为 null 并确保它们都未设置。 CONSTRAINT [CK_HasOneForiegnKey] CHECK ((FK_First!= NULL OR FK_Second != NULL) AND NOT (FK_First != NULL AND FK_Second != NULL))。

          我不确定,但我相信这将允许您一次只设置一个键。

          【讨论】:

            猜你喜欢
            • 2018-06-21
            • 2021-09-21
            • 2011-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多