您如何在数据库中表示继承?


236

我正在考虑如何在SQL Server数据库中表示复杂的结构。

考虑一个需要存储一系列对象的详细信息的应用程序,这些对象共享一些属性,但还有许多其他不常见的属性。例如,商业保险一揽子计划可能在同一份保单记录中包括责任险,汽车险,财产险和赔偿险。

在C#等中实现此功能很简单,因为您可以创建一个带有Sections集合的Policy,其中Section是根据各种封面类型的要求继承的。但是,关系数据库似乎不允许这样做。

我可以看到有两个主要选择:

  1. 创建一个Policy表,然后创建一个Sections表,其中包含所有可能的变体所需的所有字段,其中大多数都是null。

  2. 创建一个Policy表和许多Section表,每种表一个。

这两种选择似乎都不令人满意,尤其是因为有必要在所有节中编写查询时,这将涉及大量联接或大量空检查。

这种情况下的最佳做法是什么?


Answers:


430

当为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

  • 无论子类型如何,搜索所有策略都变得很困难,并且需要一堆UNIONs。

这是您必须查询所有策略的方式,而与类型无关:

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的企业应用程序体系结构的模式》


97
我也使用这种设计,但是您没有提到缺点。具体来说:1)您说不需要类型;true,但是除非您查看所有子类型表以找到匹配项,否则您无法确定行的实际类型。2)很难使主表和子类型表保持同步(例如,可以删除子类型表中的行而不是主表中的行)。3)每个主行可以有多个子类型。我使用触发器来解决1,但是2和3是非常困难的问题。实际上,如果您对合成进行建模,则3不是问题,而是用于严格继承。

19
+1 @Tibo的评论,这是一个严重的问题。实际上,类表继承会产生未规范的架构。具体表继承在哪里,我不同意具体表继承阻碍DRY的说法。SQL阻止DRY,因为它没有元编程功能。解决方案是使用数据库工具包(或编写自己的工具)来完成繁重的工作,而不是直接编写SQL(请记住,它实际上只是一种DB接口语言)。毕竟,您也不会在汇编中编写企业应用程序。
乔苏

18
@Tibo,关于第3点,您可以使用此处说明的方法:sqlteam.com/article/…,检查“ 一对一约束建模”部分。
安德鲁

4
@DanielVassallo首先,感谢您的出色回答,1怀疑一个人是否有policyId如何知道其policy_motor或policy_property?一种方法是在所有子表中搜索policyId,但我猜这是一种不好的方法,不是吗,正确的方法应该是什么?
ThomasBecker 2015年

11
我真的很喜欢你的第三选择。但是,我对SELECT的工作方式感到困惑。如果选择SELECT * FROM策略,您将获得策略ID,但仍然不知道该策略属于哪个子类型表。您是否还需要对所有子类型进行JOIN才能获得所有策略详细信息?
亚当

14

第三个选项是创建一个“策略”表,然后创建一个“ SectionsMain”表,该表存储各节类型之间共有的所有字段。然后,为每种类型的节创建其他表,这些表仅包含不常见的字段。

确定最佳选择主要取决于您拥有多少个字段以及如何编写SQL。他们都会工作。如果您只有几个字段,那么我可能会选择#1。在“很多”领域中,我倾向于#2或#3。


+1:第3个选项最接近继承模型,并且是标准化程度最高的IMO
RedFilter 2010年

您的选择3实际上就是我所说的选择2。有很多字段,有些科还会有子实体。
史蒂夫·琼斯

9

利用提供的信息,我可以对数据库进行建模以使其具有以下功能:

政策规定

  • POLICY_ID(主键)

责任

  • LIABILITY_ID(主键)
  • POLICY_ID(外键)

性质

  • PROPERTY_ID(主键)
  • POLICY_ID(外键)

...等等,因为我希望策略的每个部分都有不同的属性。否则,可能只有一个SECTIONS表,除了之外policy_id,还有一个section_type_code...

无论哪种方式,这都将允许您支持每个策略的可选部分...

我不理解您对这种方法的不满意之处-这是您在保持参照完整性且不重复数据的同时存储数据的方式。该术语是“规范化的” ...

因为SQL是基于SET的,所以它与过程/ OO编程概念相当陌生,并且要求代码从一个领域过渡到另一个领域。通常考虑使用ORM,但是它们在大容量,复杂的系统中无法很好地工作。


是的,我得到了规范化的东西;-)对于这样一个复杂的结构,有些部分很简单,而有些则具有自己的复杂子结构,尽管ORM很好,但似乎不太可能起作用。
史蒂夫·琼斯

6

此外,在Daniel Vassallo解决方案中,如果您使用SQL Server 2016+,则在某些情况下,我还会使用另一种解决方案,而不会损失很多性能。

您可以仅创建一个仅包含公共字段的表,并添加一个包含所有子类型特定字段的JSON字符串的单列。

我已经测试过该设计的继承性管理,并且我为在相关应用程序中可以使用的灵活性感到非常高兴。


1
那是一个有趣的想法。我还没有在SQL Server中使用JSON,但是在其他地方经常使用它。感谢您的注意。
史蒂夫·琼斯

5

另一种方法是使用INHERITS组件。例如:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

因此可以定义表之间的继承。


INHERITSPostgreSQL外,其他数据库是否支持?以MySQL为例?
giannis christofakis 2016年

1
@giannischristofakis:MySQL只是一个关系数据库,而Postgres是一个对象关系数据库。因此,没有MySQL不支持此功能。实际上,我认为Postgres是当前唯一支持这种继承的DBMS。
a_horse_with_no_name

2
@ marco-paulo-ollivier,OP的问题与SQL Server有关,所以我不明白为什么您提供仅适用于Postgres的解决方案。显然,无法解决问题。
mapto

@mapto这个问题已成为“如何在数据库中进行OO样式继承”的重复对象;最初与sql server有关的信息现在可能已经不相关了
Caius Jard

0

我倾向于方法1(统一的Section表),以便有效地检索整个策略及其所有部分(我认为您的系统会做很多事情)。

此外,我不知道您使用的是哪个版本的SQL Server,但是在2008+ 稀疏列中有助于在中的许多值均为NULL的情况下优化性能。

最终,您必须决定策略部分的“相似性”。除非它们之间没有实质性差异,否则我认为标准化程度更高的解决方案可能比它值得的麻烦更多……但是只有您才能打来电话。:)


将会有太多信息无法一目了然地展示整个政策,因此不再需要检索整个记录。我认为是2005年,尽管我在其他项目中使用了2008年的稀疏。
史蒂夫·琼斯

术语“统一截面表”来自哪里?Google几乎没有显示任何结果,这里已经有足够令人困惑的术语。
Stephan-v


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.