293

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

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

在 C# 等中实现这一点很简单,因为您可以创建一个包含 Sections 集合的 Policy,其中 Section 是根据各种类型的覆盖物的需要继承的。然而,关系数据库似乎并不容易做到这一点。

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

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

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

这两种选择似乎都不能令​​人满意,特别是因为有必要编写跨所有部分的查询,这将涉及大量连接或大量空检查。

这种情况的最佳实践是什么?

4

7 回答 7

520

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

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

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

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

  • 您也不能强制执行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变得更容易,因为这可以由基表处理,而不是每个子类型表独立生成它们。

  • 现在无论子类型如何,搜索所有策略变得非常容易:不需要UNIONs - 只需SELECT * FROM policies.

我认为类表方法最适合大多数情况。


这三个模型的名称来自Martin Fowler 的《企业应用程序架构模式》一书。

于 2010-08-26T20:59:23.653 回答
18

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

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

于 2010-08-26T20:15:48.953 回答
10

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

政策

  • POLICY_ID(主键)

责任

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

特性

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

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

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

我不明白你对这种方法有什么不满意的地方——这就是你在存储数据的同时保持参照完整性而不是复制数据的方式。该术语是“标准化”...

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

于 2010-08-26T20:22:50.030 回答
10

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

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

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

于 2017-09-01T12:17:09.037 回答
4

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

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

于 2013-12-04T16:10:56.387 回答
0

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

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

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

于 2010-08-26T20:22:05.930 回答
0

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

于 2019-11-01T17:22:38.773 回答