24

假设您正在建立一个数据库来存储各种车辆的碰撞测试数据。您想要存储快艇、汽车和卡丁车的碰撞测试数据。

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

假设您还想存储一些与测试不直接相关的信息(例如被测试事物的设计者的员工 ID)。这些列似乎根本不适合放在“测试”表中,特别是因为它们将在同一辆车上的所有测试中重复。

让我说明一种可能的表格排列方式,以便您了解所涉及的问题。

快艇
编号 | col_about_speedboats_but_not_tests1 | col_about_speedboats_but_not_tests2

汽车
编号 | col_about_cars_but_not_tests1 | col_about_cars_but_not_tests2

卡丁车
编号 | col_about_gokarts_but_not_tests1 | col_about_gokarts_but_not_tests2

测试
编号 | 类型 | id_in_type | col_about_all_tests1 | col_about_all_tests2
(id_in_type 将引用接下来三个表之一的 id 列,
取决于类型的值)

快艇测试
编号 | speedboat_id | col_about_speedboat_tests1 | col_about_speedboat_tests2

汽车测试
编号 | 车牌号 | col_about_car_tests1 | col_about_car_tests2

卡丁车测试
编号 | gokart_id | col_about_gokart_tests1 | col_about_gokart_tests2

这种结构有什么好处/坏处,实现这样的东西的首选方式是什么?

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

编号 | 车辆编号 | ...

使用这样的 Vehicles 表:
编号 | 类型 | id_in_type
(id_in_type 指向快艇、汽车或卡丁车的 ID)

看起来这只是一个皇家混乱。应该如何设置这样的东西?

4

6 回答 6

41

type设计id_in_type被称为多态关联。这种设计以多种方式打破了规范化规则。如果没有别的,它应该是一个危险信号,你不能声明一个真正的外键约束,因为它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 的快艇测试。

于 2009-02-16T21:42:53.013 回答
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

于 2009-02-16T21:05:00.733 回答
0

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

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

于 2009-02-16T21:00:39.513 回答
0

如果您使用SQLAlchemy,这是一个 Python 的对象关系映射器,您可以配置继承层次结构如何映射到数据库表。对象关系映射器非常适合驯服原本乏味的 SQL。

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

于 2009-02-16T21:59:39.667 回答
-1

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

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

如果您只是在“gen-spec”上搜索,您将看到的大部分内容都是面向对象的,而不是面向关系的。只要您知道如何克服对象关系阻抗不匹配,这些东西也可能很有用。

于 2009-02-16T21:58:45.193 回答
-3

您的设计是合理的,并且遵循正确的规范化规则。您可能缺少带有 Vehicle Id 和 Type 的 Vehicle 表(即 Speedboats、Cars 和 Gokarts 的“父级”......您将在其中保留“DesignedByUserId”之类的内容)。Vehicle 表和 Speedboats 表之间是一对一的关系,Vehicle 和 Speedboat/Cars/GoKarts 之间是一对一的关系(即,一辆车只能有一条快艇记录,汽车或卡丁车)...尽管大多数数据库都没有为此提供简单的执行机制。

有助于识别此类事物的一个规范化规则是字段应仅取决于表的主键。在快艇、汽车和 gokart 测试结果存储在一起的综合表中,与汽车相关的字段不仅取决于测试日期,还取决于车辆 ID 和车辆类型。测试结果表的主键是测试日期 + 车辆 id,车辆类型并不是使测试数据行唯一的原因(即是否有在 01/01/200912:30pm 对一辆特定车辆进行测试那既是快艇又是汽车……不……做不到)。

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

于 2009-02-16T21:19:13.743 回答