假设您正在设置数据库来存储各种车辆的碰撞测试数据。您想存储快艇,汽车和卡丁车的碰撞测试数据。
您可以创建三个单独的表: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 :(得分:38)
type
和id_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,并且您愿意将自己绑定到特定于数据库的功能,则它直接支持表继承:
答案 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个)处理字段取决于主键和仅主键。该规则假设主键已被正确识别(错误地定义主键太容易了)。