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

时间:2010-08-26 20:09:10

标签: sql sql-server database-design class-table-inheritance

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

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

在C#等中实现它是微不足道的,因为您可以创建一个带有Sections集合的Policy,其中Section是根据各种类型的封面所需继承的。但是,关系数据库似乎不容易这样做。

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

  1. 创建一个Policy表,然后创建一个Sections表,其中包含所有可能的变量,其中大部分都是null。

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

  3. 这两种选择似乎都不令人满意,特别是因为有必要在所有章节中编写查询,这将涉及大量连接或大量空检查。

    此方案的最佳做法是什么?

8 个答案:

答案 0 :(得分:376)

在提出SQL @Bill Karwin反模式的解决方案时,

SQL Antipatterns在他的Entity-Attribute-Value书中描述了三种继承模型。这是一个简短的概述:

单表继承(又称每层次结构表继承):

在第一个选项中使用单个表可能是最简单的设计。如您所述,许多属于子类型的属性必须在不适用这些属性的行上赋予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个。

无论类型如何,您都必须查询所有策略:

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 mentions in the other answer的解决方案。您为基类创建一个表,其中包含所有常用属性。然后,您将为每个子类型创建特定的表,其主键也用作基表的foreign key。例如:

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's本书Patterns of Enterprise Application Architecture

答案 1 :(得分:12)

第三个选项是创建一个“Policy”表,然后是一个“SectionsMain”表,它存储所有部分类型中共有的所有字段。然后为每种类型的部分创建其他表,这些表只包含不常见的字段。

确定哪个最佳取决于您拥有的字段数以及编写SQL的方式。他们都会工作。如果你只有几个领域,那么我可能会选择#1。对于“很多”的领域,我会倾向于#2或#3。

答案 2 :(得分:9)

根据提供的信息,我将数据库建模为具有以下内容:

政策

  • POLICY_ID(主键)

负债

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

属性

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

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

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

我不明白您对这种方法的不满意 - 这是您在保持参照完整性而不是复制数据的同时存储数据的方式。这个词是“规范化的”......

因为SQL是基于SET的,所以它对于程序/ OO编程概念而言是相当陌生的。需要代码从一个领域转换到另一个领域。通常会考虑ORM,但它们在大批量复杂系统中效果不佳。

答案 3 :(得分:6)

另一种方法是使用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);

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

答案 4 :(得分:2)

此外,在Daniel Vassallo解决方案中,如果您使用SQL Server 2016,我会在某些情况下使用另一种解决方案而不会导致性能大幅下降。

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

我已经测试了这个设计用于管理继承,我很高兴我可以在相关应用程序中使用它。

答案 5 :(得分:0)

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

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

最终,您必须决定政策部门的“相似”程度。除非他们有很大不同,否则我认为一个更加规范化的解决方案可能比它的价值更麻烦......但只有你才能做出这样的决定。 :)

答案 6 :(得分:0)

或者,考虑使用本机支持丰富的数据结构和嵌套的文档数据库(例如MongoDB)。

答案 7 :(得分:-1)