22

假设您设置了一个数据库来存储各种车辆的碰撞测试数据。你想存储快艇,汽车和卡丁车碰撞测试的数据。数据库设计中的继承问题

您可以创建三个独立的表格: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) 

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

+0

可能的重复[如何有效地建模数据库中的继承?](http://stackoverflow.com/questions/190296/how-do-you-effectively-model-inheritance-in-a-database) – Musa 2014-08-25 13:45:23

回答

37

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它引用Vehicles.vehicle_id,摆脱在每个测试分型表vehicle_id外键的,但将允许异常,如快艇测试引用了gokart的id。

0

我会把它分成不同的表格,例如:车辆(ID,类型等)VehicleAttributes()VehicleID,AttributeID,Value),CrashTestInfo(VehicleID,CrashtestID,Date等)CrashtestAttributes(CrashTestID,AttributeID,Value)

或者不是属性,应记录类似的细节。

+0

这是实体 - 属性 - 价值设计,这对于OP的情景来说是过度的。 – 2009-02-16 21:44:56

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

+0

我想补充一点,具体参考原始问题中提到的皇室混乱情况,即外键将从特定车型指向抽象车辆表。即speedboat(vehicle_id FK,speedboat_specific_column1等) – Robin 2009-02-16 21:36:48

-3

你的设计是合理的,是继正确的规范化规则。你可能会遗漏一个带有车辆标识和类型的车辆表(例如,快艇,汽车和Gokarts的“父母”......你可以在其中保存诸如“DesignedByUserId”之类的东西)。车辆表和快艇之间是一对一的关系,车与快艇/汽车/ GoKarts之间有一对一的关系(即一辆车只能有一条快艇记录,汽车或卡丁车)...虽然大多数数据库并没有提供一个简单的执法机制。

一个规范化规则,可帮助确定这些事情是一个字段应该只在表的主键依赖。在将快艇,汽车和gokart测试结果存储在一起的统一表格中,汽车相关领域不仅取决于测试日期,还取决于车辆ID和车辆类型。测试结果表的主要关键是测试日期+车辆ID,车辆类型不是使得测试数据行具有唯一性的因素(也就是说,在某一特定车辆上是否有在01/01/200912:30进行的测试那既是快艇又是汽车......不行......不能这样做)。

我不解释规范化规则particularily很好......但3/4/5的正常形式的规则总是混淆了我,当我读到的正式描述。其中一个(3rd/4th/5th)根据主键和仅主键处理字段。该规则假定主键已被正确识别(错误地定义主键太容易)。

+1

-1,因为多态关联设计(`type`和`id_in_type`事物)不是标准化设计。 – 2009-02-16 21:28:54

+0

呃......见http://en.wikipedia.org/wiki/Fourth_normal_form。比萨的例子是相当合理的。 – user53794 2009-02-16 22:13:24

+1

你说{test_id,type} - > - > {id_in_type}传递4NF,因此{test_id,type}是一个超级键吗?我在谈论一个关系的基本定义,其中每个属性表示一个“事物”的值 - 但id_in_type是三种不同的事物。 – 2009-02-17 01:41:44

0

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

最好的文章,国际海事组织,讨论ER建模方面根规格。如果您知道如何将ER模型转换为关系模型,然后再转换为SQL表格,那么一旦他们向您展示如何在ER中建模gen-spec,您就会知道该怎么做。

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

0

如果您使用SQLAlchemy,Python的对象关系映射器,您可以使用configure how inheritance hierarchies are mapped to database tables。对象关系映射器对驯服繁琐的SQL非常有用。

您的问题可能很适合垂直表格。不是将所有内容都存储在模式中,而是将对象的类型和主键存储在一个表中,并将键/值元组存储在另一个表中的每个对象中。如果您确实在存储汽车测试,则此设置将使添加新类型结果变得更容易。