当为SQL Entity-Attribute-Value反模式提出解决方案时,@ Bill Karwin在他的《SQL Antipatterns》一书中描述了三种继承模型。这是一个简短的概述:
单表继承(又名表每个层次结构继承):
如您的第一种选择那样使用单个表可能是最简单的设计。如您所提到的,许多特定于子类型的NULL
属性必须在不适用这些属性的行上赋予一个值。使用此模型,您将有一个策略表,看起来像这样:
+------+---------------------+----------+----------------+------------------+
| id | date_issued | type | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
| 1 | 2010-08-20 12:00:00 | MOTOR | 01-A-04004 | NULL |
| 2 | 2010-08-20 13:00:00 | MOTOR | 02-B-01010 | NULL |
| 3 | 2010-08-20 14:00:00 | PROPERTY | NULL | Oxford Street |
| 4 | 2010-08-20 15:00:00 | MOTOR | 03-C-02020 | NULL |
+------+---------------------+----------+----------------+------------------+
\------ COMMON FIELDS -------/ \----- SUBTYPE SPECIFIC FIELDS -----/
使设计保持简单是一个加号,但是此方法的主要问题如下:
在添加新的子类型时,您必须更改表以容纳描述这些新对象的属性。当您有许多子类型时,或者计划定期添加子类型时,这很快就会成为问题。
数据库将无法执行哪些属性适用,哪些属性不适用,因为没有元数据来定义哪些属性属于哪些子类型。
您也不能NOT NULL
对应该为强制性的子类型的属性进行强制。您将不得不在您的应用程序中处理此问题,这通常是不理想的。
具体表继承:
解决继承的另一种方法是为每个子类型创建一个新表,并重复每个表中的所有公共属性。例如:
--// Table: policies_motor
+------+---------------------+----------------+
| id | date_issued | vehicle_reg_no |
+------+---------------------+----------------+
| 1 | 2010-08-20 12:00:00 | 01-A-04004 |
| 2 | 2010-08-20 13:00:00 | 02-B-01010 |
| 3 | 2010-08-20 15:00:00 | 03-C-02020 |
+------+---------------------+----------------+
--// Table: policies_property
+------+---------------------+------------------+
| id | date_issued | property_address |
+------+---------------------+------------------+
| 1 | 2010-08-20 14:00:00 | Oxford Street |
+------+---------------------+------------------+
此设计将基本上解决为单表方法确定的问题:
强制属性现在可以通过强制执行NOT NULL
。
添加新的子类型需要添加一个新表,而不是在现有表中添加列。
也没有风险为特定的子类型(例如,vehicle_reg_no
属性策略的字段)设置了不合适的属性。
不需要type
单表方法中的属性。现在,该类型由元数据定义:表名。
但是,此模型还具有一些缺点:
通用属性与特定于子类型的属性混合在一起,并且没有简单的方法来识别它们。数据库也不知道。
定义表时,必须为每个子类型表重复通用属性。那绝对不是DRY。
无论子类型如何,搜索所有策略都变得很困难,并且需要一堆UNION
s。
这是您必须查询所有策略的方式,而与类型无关:
SELECT date_issued, other_common_fields, 'MOTOR' AS type
FROM policies_motor
UNION ALL
SELECT date_issued, other_common_fields, 'PROPERTY' AS type
FROM policies_property;
请注意,添加新的子类型将如何要求修改上述查询,并UNION ALL
为每个子类型增加一个查询。如果忘记了此操作,很容易导致应用程序中的错误。
类表继承(又名表每种类型继承):
这是@David在另一个答案中提到的解决方案。您为基类创建一个表,其中包含所有公共属性。然后,您将为每个子类型创建特定的表,这些子类型的主键也用作基表的外键。例:
CREATE TABLE policies (
policy_id int,
date_issued datetime,
-- // other common attributes ...
);
CREATE TABLE policy_motor (
policy_id int,
vehicle_reg_no varchar(20),
-- // other attributes specific to motor insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
CREATE TABLE policy_property (
policy_id int,
property_address varchar(20),
-- // other attributes specific to property insurance ...
FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);
此解决方案解决了其他两种设计中发现的问题:
可以使用强制执行强制属性NOT NULL
。
添加新的子类型需要添加一个新表,而不是在现有表中添加列。
没有为特定的子类型设置不合适的属性的风险。
不需要该type
属性。
现在,公共属性不再与子类型特定的属性混合。
最后,我们可以保持干燥。创建表时,无需为每个子类型表重复通用属性。
管理id
策略的自动递增变得更加容易,因为它可以由基表处理,而不是由每个子类型表独立生成。
现在搜索所有策略,无论其子类型如何都变得非常容易:不需要UNION
-只需一个即可SELECT * FROM policies
。
我认为类表方法在大多数情况下是最合适的。
这三个模型的名称来自Martin Fowler的《企业应用程序体系结构的模式》。