像数据库设计中的继承之类的东西

时间:2009-02-16 20:55:45

标签: database-design polymorphic-associations class-table-inheritance

假设您正在设置数据库来存储各种车辆的碰撞测试数据。您想存储快艇,汽车和卡丁车的碰撞测试数据。

您可以创建三个单独的表:SpeedboatTests,CarTests和GokartTests。但是每个表中的很多列都是相同的(例如,执行测试的人员的员工ID,碰撞的方向(正面,侧面,后面)等)。但是,很多列都会有所不同,因此您不希望将所有测试数据放在一个表中,因为您将拥有相当多的列,对于快艇来说总是为空,相当多的列始终是对于汽车来说是零,而对于卡丁车来说,相当多的都是空的。

假设您还想存储一些与测试没有直接关系的信息(例如正在测试的东西的设计者的员工ID)。这些列根本不适合放入“测试”表,特别是因为它们将在同一车辆的所有测试中重复使用。

让我说明一种可能的表格排列,以便您可以看到所涉及的问题。

Speedboats
id | col_about_speedboats_but_not_tests1 | col_about_speedboats_but_not_tests2

Cars
id | col_about_cars_but_not_tests1 | col_about_cars_but_not_tests2

Gokarts
id | col_about_gokarts_but_not_tests1 | col_about_gokarts_but_not_tests2

Tests
id | type | id_in_type | col_about_all_tests1 | col_about_all_tests2
(id_in_type will refer to the id column of one of the next three tables,
depending on the value of type)

SpeedboatTests
id | speedboat_id | col_about_speedboat_tests1 | col_about_speedboat_tests2

CarTests
id | car_id | col_about_car_tests1 | col_about_car_tests2

GokartTests
id | gokart_id | col_about_gokart_tests1 | col_about_gokart_tests2

这个结构的优点/缺点是什么,以及实现这类结果的首选方法是什么?

如果还有一些信息适用于您希望在车辆表中使用的所有车辆,该怎么办?那么CarTests表会看起来像......

id | vehicle_id | ...

With a Vehicles table like this:
id | type | id_in_type
(with id_in_type pointing to the id of either a speedboat, car, or go-kart)

这似乎是一场皇家般的混乱。应该如何设置这样的东西?

6 个答案:

答案 0 :(得分:38)

typeid_in_type设计称为Polymorphic Associations。这种设计以多种方式打破了规范化规则。如果没有别的,它应该是一个红色标志,你不能声明一个真正的外键约束,因为id_in_type可以引用几个表中的任何一个。

这是定义表格的更好方法:

  • 制作一个抽象表Vehicles,为所有车辆子类型和车辆测试提供抽象参考点。
  • 每个车辆子类型都有一个不会自动增加的主键,而是引用Vehicles
  • 每个测试子类型都有一个不会自动递增的主键,而是引用Tests
  • 每个测试子类型还具有相应车辆子类型的外键。

以下是DDL示例:

CREATE TABLE Vehicles (
 vehicle_id INT AUTO_INCREMENT PRIMARY KEY
);

CREATE TABLE Speedboats (
 vehicle_id INT PRIMARY KEY,
 col_about_speedboats_but_not_tests1 INT,
 col_about_speedboats_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Cars (
 vehicle_id INT PRIMARY KEY,
 col_about_cars_but_not_tests1 INT,
 col_about_cars_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Gokarts (
 vehicle_id INT PRIMARY KEY,
 col_about_gokarts_but_not_tests1 INT,
 col_about_gokarts_but_not_tests2 INT,
 FOREIGN KEY(vehicle_id) REFERENCES Vehicles(vehicle_id)
);

CREATE TABLE Tests (
 test_id INT AUTO_INCREMENT PRIMARY KEY,
 col_about_all_tests1 INT,
 col_about_all_tests2 INT
);

CREATE TABLE SpeedboatTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_speedboat_tests1 INT,
 col_about_speedboat_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Speedboats(vehicle_id)
);

CREATE TABLE CarTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_car_tests1 INT,
 col_about_car_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Cars(vehicle_id)
);

CREATE TABLE GokartTests (
 test_id INT PRIMARY KEY,
 vehicle_id INT NOT NULL,
 col_about_gokart_tests1 INT,
 col_about_gokart_tests2 INT,
 FOREIGN KEY(test_id) REFERENCES Tests(test_id),
 FOREIGN KEY(vehicle_id) REFERENCES Gokarts(vehicle_id)
);

您也可以声明引用Tests.vehicle_id的{​​{1}}并删除每个测试子类型表中的vehicle_id外键,但这样会导致异常,例如引用gokart的快艇测试标识。

答案 1 :(得分:14)

为了将继承层次结构映射到数据库表,我认为Martin Fowler在他的“企业应用程序架构模式”一书中很好地阐述了替代方案。

http://martinfowler.com/eaaCatalog/singleTableInheritance.html

http://martinfowler.com/eaaCatalog/classTableInheritance.html

http://martinfowler.com/eaaCatalog/concreteTableInheritance.html

如果子类的附加字段/列数很少,那么单表继承通常最容易处理。

如果您正在为您的数据库使用PostgreSQL,并且您愿意将自己绑定到特定于数据库的功能,则它直接支持表继承:

http://www.postgresql.org/docs/8.3/static/ddl-inherit.html

答案 2 :(得分:0)

我会将其分解为不同的表格,例如Vehicle(ID,type等)VehicleAttributes()VehicleID,AttributeID,Value),CrashTestInfo(VehicleID,CrashtestID,Date等)CrashtestAttributes(CrashTestID,AttributeID,Value)

或者不是属性,为每组应该记录的相似细节分开表。

答案 3 :(得分:0)

如果您使用的是SQLAlchemy(Python的对象关系映射器),您可以configure how inheritance hierarchies are mapped to database tables。对象关系映射器有利于驯服否则繁琐的SQL。

您的问题可能非常适合垂直表格。不是将所有内容存储在模式中,而是将对象的类型和主键存储在一个表中,并将键/值元组存储在另一个表中的每个对象中。如果您真的存储汽车测试,这种设置可以更容易地添加新的结果。

答案 4 :(得分:-1)

对“gen-spec关系建模”进行谷歌搜索。您将找到有关如何设置存储广义实体属性的表(OO程序员可能称为超类)的文章,每个专用实体(子类)的单独表,以及如何使用外键来链接它一起来。

最好的文章,IMO,在ER建模方面讨论gen-spec。如果您知道如何将ER模型转换为关系模型,然后将其转换为SQL表,那么一旦他们向您展示如何在ER中对gen-spec建模,您就会知道该怎么做。

如果你只是谷歌“gen-spec”,你会看到的大多数是面向对象的,而不是面向关系的。只要您知道如何克服对象关系阻抗不匹配,这些东西也可能很有用。

答案 5 :(得分:-3)

您的设计合理,并遵循正确的规范化规则。你可能会错过一个带有车辆ID和类型的车辆表(即快艇,汽车和Gokarts的“父”......你会保留像“DesignedByUserId”这样的东西)。在Vehicle表和Speedboats表之间是一对一的关系,在Vehicle和Speedboat / Cars / GoKarts之间存在1对1的关系(即,一辆车只能有1条快艇记录,汽车或卡丁车)...虽然大多数数据库都没有为此提供简单的执行机制。

有助于识别这类事物的一个规范化规则是字段应仅依赖于表的主键。在快艇,汽车和gokart测试结果存储在一起的综合表中,汽车相关领域不仅取决于测试日期,还取决于车辆id和车辆类型。测试结果表的主键是测试日期+车辆ID,并且车辆类型不是使测试数据行唯一的原因(即,无论如何在01/01/200912:30 pm在一个特定车辆上进行测试这既是快艇又是汽车......不能......不能做到。)

我并没有特别好地解释规范化规则......但是当我阅读正式描述时,第3 /第4 /第5范式规则总是让我感到困惑。其中一个(第3 /第4 /第5个)处理字段取决于主键和仅主键。该规则假设主键已被正确识别(错误地定义主键太容易了)。