在关系数据库中维护子类完整性

时间:2008-12-01 23:32:49

标签: sql sql-server database-design oop

假设我有一张代表超级班的表,学生。然后我有N个表格代表该对象的子类(运动员音乐家等)。我如何表达一个约束,以便学生必须在一个(不多于,不少于)子类中建模?

有关评论的澄清:

  • 这是手动维护,而不是通过ORM包。
  • 与SQL Server相关的项目(但看到通用解决方案会很高兴)
  • 这可能不是最好的例子。我们可以考虑一些关于子类化的场景,我碰巧发明了这个学生/运动员的例子。

A)以真正的面向对象的方式,超类可以自己存在,不需要在任何子类中建模。

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

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

感谢大家的投入,特别是比尔。

7 个答案:

答案 0 :(得分:3)

每个学生记录都有一个SubClass列(假设为了参数,它是一个CHAR(1))。 {A =运动员,M =音乐家......}

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

使用StudentID和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)
);

答案 1 :(得分: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可以很容易地成为一个整数,我只是将它显示为char用于说明目的。

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

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

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

答案 2 :(得分:1)

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

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

我希望那些资源仍在那里。

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

在开始设计数据库之前,有必要提出一个概念数据模型,它将存储在数据库中的值连接回主题。制作概念数据模型实际上是数据分析,而不是数据库设计。有时很难将分析和设计分开。

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

如果我读了你的话,你给一个有可能担任音乐家等几个角色的学生的案例可能并没有说明你感兴趣的案例。您似乎对子类互斥的情况感兴趣。也许车辆可能是汽车,卡车或摩托车的情况可能更容易讨论。

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

答案 3 :(得分:0)

有趣的问题。当然FK限制是针对子表的,因此必须有一个学生。

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

你可以写一个现在检查的应用程序然后你是否真的关心这个问题。我认为最大的恐惧是删除。有人可以删除子表条目,但不能删除学生。您可以使用触发器检查何时从子表中删除项目,因为这可能是最大的问题。

我有一个带有每个子类层次结构的表的数据库,如下所示。我正确使用Hibernate及其映射,因此它会自动删除所有内容。如果通过'hand'执行此操作,那么我将确保始终删除具有适当级联的父级hehe :))

答案 4 :(得分:0)

谢谢,比尔。你让我想到了......

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

这里唯一缺少的部分是可以在没有子类的情况下建模超类。即使您强制使用代码列,它也可能只是一个空连接。这可以通过添加一个约束来解决,该约束表明超类的id存在于子类表中​​的ids的并集中。如果在事务中间强制执行约束,则插入会因这两个约束而变得有点毛茸茸。那或者只是不要担心未经验证的对象。

编辑:Bleh,这是一个很好听的想法...但是不支持引用其他表的子查询这一事实。至少不在SQL Server中。

答案 5 :(得分:0)

  

可以通过添加超类的ID存在于联合中的约束来修复   子类表中的ID。

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

答案 6 :(得分:0)

我可能会添加一个Check Constraint 将ForeignKeys创建为Nullable。 添加一个检查以确保它们都不为空,并确保它们都没有设置。 CONSTRAINT [CK_HasOneForiegnKey] CHECK((FK_First!= NULL或FK_Second!= NULL)AND NOT(FK_First!= NULL AND FK_Second!= NULL))。

我不确定,但我相信这会让你一次只能设置一个键。