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

Ste*_*nes 209 sql database inheritance database-design class-table-inheritance

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

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

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

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

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

  2. 创建一个Policy表和许多Section表,每个表对应一种封面.

这两种替代方案似乎都不令人满意,特别是因为必须在所有Sections中编写查询,这将涉及大量连接或大量空检查.

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

Dan*_*llo 397

@Bill Karwin在提出SQL 实体 - 属性 - 值反模式的解决方案时,在他的SQL反模式书中描述了三种继承模型.这是一个简短的概述:

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

在第一个选项中使用单个表可能是最简单的设计.如您所述,许多属于子类型的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 -----/
Run Code Online (Sandbox Code Playgroud)

保持设计简单是一个优点,但这种方法的主要问题如下:

  • 在添加新的子类型时,您必须更改表以适应描述这些新对象的属性.当您有许多子类型,或者您计划定期添加子类型时,这很快就会出现问题.

  • 数据库将无法强制应用哪些属性,哪些属性不适用,因为没有元数据来定义哪些属性属于哪些子类型.

  • 您也无法强制执行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    |   
+------+---------------------+------------------+
Run Code Online (Sandbox Code Playgroud)

这种设计基本上解决了单表方法确定的问题:

  • 现在可以使用强制属性NOT NULL.

  • 添加新子类型需要添加新表而不是向现有表添加列.

  • 也没有为特定子类型设置不适当属性的风险,例如vehicle_reg_no属性策略的字段.

  • type单表方法中不需要属性.现在,类型由元数据定义:表名.

然而,这个模型也有一些缺点:

  • 公共属性与子类型特定属性混合在一起,并且没有简单的方法来识别它们.数据库也不会知道.

  • 定义表时,必须重复每个子类型表的公共属性.那肯定不是干的.

  • 搜索所有策略而不管子类型变得困难,并且需要一堆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;
Run Code Online (Sandbox Code Playgroud)

请注意添加新子类型将需要修改上述查询,并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)
);
Run Code Online (Sandbox Code Playgroud)

该解决方案解决了其他两种设计中发现的问题:

  • 强制属性可以强制执行NOT NULL.

  • 添加新子类型需要添加新表而不是向现有表添加列.

  • 没有为特定子类型设置不适当属性的风险.

  • 不需要type属性.

  • 现在,公共属性不再与子类型特定属性混合.

  • 我们终于可以保持干爽.创建表时,无需为每个子类型表重复公共属性.

  • 管理id策略的自动递增变得更容易,因为这可以由基表处理,而不是每个子类表独立生成它们.

  • 现在,无论子类型如何搜索所有策略都变得非常简单:不需要UNION- 只需要一个SELECT * FROM policies.

我认为类表方法在大多数情况下是最合适的.


这三个模型的名称来自Martin Fowler的书籍"企业应用程序架构模式".

  • 我也在使用这种设计,但你没有提到这些缺点.具体来说:1)你说你不需要这种类型; true,但除非查看所有子类型表以查找匹配项,否则无法识别行的实际类型.2)很难使主表和子类表保持同步(例如,可以删除子类型表中的行而不是主表中的行).3)每个主行可以有多个子类型.我使用触发器来解决1,但2和3是非常困难的问题.实际上,如果您对组合进行建模,则3不是问题,而是严格继承. (89认同)
  • 对@ Tibo的评论+1,这是一个严重的问题.类表继承实际上产生了非标准化模式.具体表继承没有,我不同意具体表继承阻碍DRY的论点.*SQL*阻碍了DRY,因为它没有元编程功能.解决方案是使用Database Toolkit(或编写自己的)来完成繁重的工作,而不是直接编写SQL(记住,它实际上只是一种DB接口语言).毕竟,您也不会在程序集中编写企业应用程序. (17认同)
  • @Tibo,关于第3点,你可以使用这里解释的方法:http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server,检查*建模一对一约束*部分. (15认同)
  • 我真的很喜欢你的第三个选择.但是,我很困惑SELECT如何工作.如果您选择SELECT*FROM策略,则会返回策略ID,但您仍然不知道该策略属于哪个子类型表.为了获得所有的政策细节,您是否还需要与所有子类型进行联接? (9认同)
  • @DanielVassallo首先要感谢惊人的答案,1怀疑一个人是否有策略如何知道它的policy_motor或policy_property?一种方法是在所有子表中搜索policyId,但我想这不是坏方法,应该采用什么方法? (3认同)
  • 我同意一个很好的答案。但我感兴趣的是,选项 3 在策略表和策略类型表之间将具有 1 对 1 的关系。您对数据库规范化有何看法? (2认同)
  • @JoSo 谁能告诉我如何插入到类表继承模型中?例如,`INSERT INTO policy_property (date_issued) VALUES (2010-08-20 12:00:00);` 不起作用,因为 `date_issued` 不是 `policy_property` 的字段属性,即使它是超类`策略` (2认同)

Dav*_*vid 13

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

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


ove*_*mer 10

此外,在 Daniel Vassallo 解决方案中,如果您使用 SQL Server 2016+,则我在某些情况下使用了另一种解决方案,而不会显着降低性能。

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

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

  • 这对于您不打算建立索引的数据来说非常有用...如果您打算在 WHERE 子句等中使用列,您将需要对它们建立索引,而 JSON 模式会限制您这样做。 (6认同)

OMG*_*ies 9

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

政策

  • POLICY_ID(主键)

负债

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

性能

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

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

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

我不明白你对这种方法的不满意 - 这是你在保持参照完整性而不是复制数据的同时存储数据的方式.这个词是"标准化的"......

因为SQL是基于SET的,所以它对于程序/ OO编程概念来说相当陌生,并且需要代码从一个领域转换到另一个领域.通常会考虑ORM,但它们在大批量复杂系统中不能很好地工作.


Mar*_*ier 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);
Run Code Online (Sandbox Code Playgroud)

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

  • 除了 *PostgreSQL* 之外,其他数据库是否支持“INHERITS”?*MySQL* 例如? (3认同)
  • @giannischristofakis:MySQL只是一个关系数据库,而Postgres是一个对象关系数据库。所以,没有MySQL不支持这个。事实上,我认为Postgres是当前唯一支持这种继承类型的DBMS。 (3认同)
  • @ marco-paulo-ollivier,OP的问题与SQL Server有关,所以我不明白为什么您提供仅适用于Postgres的解决方案。显然,不能解决问题。 (2认同)
  • @mapto 这个问题已经变成了“如何在数据库中进行 OO 风格继承”的欺骗目标;它最初是关于 sql server 的,现在可能已经无关紧要了 (2认同)