你怎么能在数据库中表示inheritance?

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

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

在C#中实现这一点是很简单的,因为您可以创build一个包含部分集合的策略,其中部分根据各种types的封面的需要而被inheritance。 但是,关系数据库似乎不容易这样做。

我可以看到有两个主要的select:

  1. 创build一个策略表,然后创build一个Sections表,其中包含所有必需的字段,用于所有可能的变化,其中大部分将是空的。

  2. 创build一个政策表和大量的科表,每种types的封面。

这两种替代方法看起来都不尽如人意,特别是因为有必要在所有部分中编写查询,这将涉及大量的联接或多次空值检查。

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

当向SQL Entity-Attribute-Value反模式提出解决scheme时, @Bill Karwin在其SQL反模式书中描述了三种inheritance模型。 这是一个简要的概述:

单表inheritance(又名Table Per Hierarchy Inheritance):

在第一个选项中使用单个表格可能是最简单的devise。 正如你所提到的,许多子types特定的属性在这些属性不适用的行上必须被赋予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 -----/ 

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

  • 当涉及添加新的子types时,您将不得不修改表以适应描述这些新对象的属性。 如果您有很多子types,或者您打算定期添加子types,这可能会很快出现问题。

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

  • 您也不能在应该是强制性的子types的属性上强制使用NOT NULL 。 你将不得不在你的应用程序中处理这个问题,这通常是不理想的。

混凝土表inheritance:

解决inheritance问题的另一种方法是为每个子types创build一个新表,重复每个表中的所有常用属性。 例如:

 --// 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 | +------+---------------------+------------------+ 

这个devise从根本上解决了单表法的问题:

  • 强制属性现在可以用NOT NULL强制执行。

  • 添加新的子types需要添加一个新的表格,而不是将列添加到现有的表格。

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

  • 在单个表格方法中不需要type属性。 该types现在由元数据定义:表名称。

但是这个模型也有一些缺点:

  • 常见的属性与特定于子types的属性混合在一起,并且没有简单的方法来识别它们。 数据库也不知道。

  • 定义表格时,必须重复每个子types表格的通用属性。 这绝对不是干的 。

  • search所有的政策,不pipe子types变得困难,并将需要一堆的UNION

这是你将不得不查询所有的政策,不pipetypes:

 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; 

请注意,如何添加新的子types将需要使用每个子types的附加UNION ALL修改上述查询。 如果忘记了这个操作,这很容易导致应用程序出错。

类表inheritance(aka Table Per Type Inheritance):

这是@David在另一个答案中提到的解决scheme。 您为您的基类创build了一个表,其中包含所有常用属性。 然后,您将为每个子types创build特定的表,其主键也作为基表的外键 。 例:

 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) ); 

该解决scheme解决了其他两种devise中遇到的问题:

  • 强制属性可以用NOT NULL强制执行。

  • 添加新的子types需要添加一个新的表格,而不是将列添加到现有的表格。

  • 没有为特定子types设置不适当的属性的风险。

  • 不需要type属性。

  • 现在,常用属性不再与子types特定的属性混合在一起。

  • 最后,我们可以保持干爽。 创build表格时,不必为每个子types表重复共同的属性。

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

  • 现在search所有策略,不pipe子types如何变得非常简单:不需要UNION – 只是一个SELECT * FROM policies

我认为在大多数情况下,class级表方法是最合适的。


这三个模型的名字来自Martin Fowler的 企业应用架构模式 。

第三个选项是创build一个“策略”表,然后是一个“SectionsMain”表,它存储所有types的部分之间通用的所有字段。 然后为每个types的部分创build其他表,只包含不共同的字段。

确定哪一个最好取决于你有多less个字段以及你想如何编写你的SQL。 他们都会工作。 如果你只有几个领域,那么我可能会去#1。 有了“很多”的领域,我会倾向于#2或#3。

随着所提供的信息,我会build模数据库有以下几点:

政策

  • POLICY_ID(主键)

负债

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

性能

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

…等等,因为我希望有与政策的每个部分相关的不同属性。 否则,可能会有一个policy_id表,除了policy_id ,还有一个section_type_code

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

我不明白你对这种方法不满意的地方 – 这是你如何存储数据,同时保持参照完整性,而不是复制数据。 这个词是“正常化”…

因为SQL是基于SET的,所以它与程序/ OO编程概念相当陌生,并且要求代码从一个领域转换到另一个领域。 ORM经常被考虑,但是它们在高容量,复杂的系统中不能很好地工作。

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

因此可以在表之间定义一个inheritance。

看看我在这里给出的答案

stream利的NHibernate与合成键的一对一映射

我倾向于方法#1(一个统一的分区表),为了有效地检索所有的部分(我假设你的系统将会做很多)的整个政策。

此外,我不知道你正在使用什么版本的SQL Server,但在2008+ 稀疏列帮助优化性能的情况下, 列中的许多值将为NULL。

最终,你必须决定政策部分是多么“相似”。 除非它们大相径庭,否则我认为一个更加正常化的解决scheme可能比它的价值更麻烦,但是只有你能够这样做。 🙂

此外,在Daniel Vassallo解决scheme中,如果您使用SQL Server 2016,还有另一种解决scheme,我在某些情况下使用时不会丢失大量的性能。

您只能创build一个只有普通字段的表格,并添加一个包含所有子types特定字段的JSONstring的列。

我已经testing了这个pipe理inheritance的devise,我很高兴我可以在相关的应用程序中使用它的灵活性。