你怎么能在数据库中表示inheritance?
我正在考虑如何在SQL Server数据库中表示一个复杂的结构。
考虑一个需要存储一系列对象的细节的应用程序,这些对象共享一些属性,但有许多其他属性不常见。 例如,商业保险一揽子计划可能包括同一保单logging中的责任,动机,财产和赔偿保障。
在C#中实现这一点是很简单的,因为您可以创build一个包含部分集合的策略,其中部分根据各种types的封面的需要而被inheritance。 但是,关系数据库似乎不容易这样做。
我可以看到有两个主要的select:
-
创build一个策略表,然后创build一个Sections表,其中包含所有必需的字段,用于所有可能的变化,其中大部分将是空的。
-
创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,我很高兴我可以在相关的应用程序中使用它的灵活性。