如何在数据库中表示继承?[关闭]

How can you represent inheritance in a database? [closed]

提问人:Steve Jones 提问时间:8/27/2010 最后编辑:Steve Jones 更新时间:3/27/2023 访问量:154802

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章用事实和引文来回答。

上个月关闭。

社群在上个月审查了是否要重新讨论这个问题,并关闭了这个问题:

原始关闭原因未解决

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

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

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

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

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

  2. 创建一个“策略”表和多个“部分”表,每种类型的封面对应一个。

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

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

sql-server 数据库设计 类表继承

答:

19赞 David 8/27/2010 #1

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

决定哪个最好主要取决于你有多少个字段以及你想如何编写你的 SQL。它们都会起作用。如果你只有几个字段,那么我可能会选择#1。对于“很多”字段,我会倾向于 #2 或 #3。

评论

0赞 Steve Jones 8/27/2010
您的选项 #3 实际上正是我所说的选项 #2 的意思。有许多字段,某些部分也会有子实体。
0赞 Dan J 8/27/2010 #2

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

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

最终,您必须决定策略部分的“相似程度”。除非它们有很大差异,否则我认为更规范化的解决方案可能比它的价值更麻烦......但只有你能打这个电话。:)

评论

0赞 Steve Jones 8/27/2010
会有太多的信息无法一次性呈现整个保单,因此永远没有必要检索整个记录。我认为是 2005 年,尽管我在其他项目中使用了 2008 年的稀疏。
2赞 Stephan-v 4/2/2019
“统一截面表”一词从何而来?谷歌几乎没有显示任何结果,这里已经有足够多令人困惑的术语了。
10赞 OMG Ponies 8/27/2010 #3

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

政策

  • POLICY_ID(主键)

负债

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

性能

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

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

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

我不明白你对这种方法有什么不满意的地方 - 这就是你存储数据的方式,同时保持引用完整性而不是复制数据。该术语是“规范化”的......

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

评论

0赞 Steve Jones 8/27/2010
是的,我明白了正常化的东西;-)对于这样一个复杂的结构,有些部分很简单,有些部分有自己的复杂子结构,ORM 似乎不太可能工作,尽管它会很好。
571赞 Daniel Vassallo 8/27/2010 #4

@Bill Karwin 在他的 SQL 反模式一书中描述了三种继承模型,当时他提出了 SQL 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

但是,此模型也有一些缺点:

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

  • 定义表时,必须重复每个子类型表的通用属性。那绝对不是干枯的。

  • 无论子类型如何,搜索所有策略都变得困难,并且需要一堆 s。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在另一个答案中提到的解决方案。为基类创建一个表,其中包含所有通用属性。然后,您将为每个子类型创建特定的表,其主键也用作基表的外键。例:

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

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

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


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

评论

130赞 2/13/2013
我也在使用这种设计,但你没有提到缺点。具体来说:1)你说你不需要类型;true,但除非查看所有子类型表以查找匹配项,否则无法识别行的实际类型。2) 很难使主表和子类型表保持同步(例如,可以删除子类型表中的行,而不是主表中的行)。3) 每个主控行可以有多个子类型。我使用触发器来解决 1,但 2 和 3 是非常困难的问题。实际上,如果你对组合进行建模,3 不是问题,但是为了严格继承。
22赞 Jo So 12/11/2013
+1 对于@Tibo的评论,这是一个严重的问题。类表继承实际上会产生一个非规范化的架构。具体表继承没有,我不同意具体表继承阻碍 DRY 的论点。SQL 阻碍了 DRY,因为它没有元编程工具。解决方案是使用数据库工具包(或编写自己的工具包)来完成繁重的工作,而不是直接编写 SQL(请记住,它实际上只是一种数据库接口语言)。毕竟,您也不会在汇编中编写企业应用程序。
22赞 Andrew 2/3/2015
@Tibo,关于第 3 点,您可以使用此处解释的方法:sqlteam.com/article/...,请查看 Modeling One-to-Either Constraints 部分。
7赞 ThomasBecker 3/3/2015
@DanielVassallo 首先感谢您的惊人回答,1 怀疑一个人是否有保单 Id,如何知道其policy_motor还是policy_property?一种方法是在所有子表中搜索 policyId,但我想这是不好的方法,不是吗,正确的方法应该是什么?
21赞 Adam 3/8/2016
我真的很喜欢你的第三个选择。但是,我对 SELECT 将如何工作感到困惑。如果 SELECT * FROM 策略,您将返回策略 ID,但您仍然不知道该策略属于哪个子类型表。难道您仍然需要对所有子类型执行 JOIN 才能获取所有策略详细信息吗?
6赞 Marco Paulo Ollivier 12/5/2013 #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);

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

评论

3赞 giannis christofakis 3/11/2016
除了PostgreSQL之外,其他数据库是否支持?例如MySQLINHERITS
3赞 11/12/2016
@giannischristofakis:MySQL 只是一个关系数据库,而 Postgres 是一个对象关系数据库。所以,没有MySQL不支持这个。事实上,我认为 Postgres 是当前唯一支持此类继承的 DBMS。
11赞 mapto 5/24/2019
@marco-paulo-ollivier,OP 的问题是关于 SQL Server 的,所以我不明白为什么您提供仅适用于 Postgres 的解决方案。显然,没有解决问题。
3赞 Caius Jard 1/21/2020
@mapto这个问题已经变成了“如何在数据库中进行OO风格的继承”的欺骗目标;它最初是关于SQL Server的,现在可能已经无关紧要了
0赞 defraggled 7/3/2021
似乎 pg 功能允许更优雅的查询,但是表/列本身仍然完全以具体的继承方式设置:重复所有常见属性。我不认为这消除了具体继承已经提出的任何缺陷INHERITS
13赞 overcomer 9/1/2017 #6

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

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

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

评论

6赞 MatBailie 8/31/2021
这对于您不打算编制索引的数据非常有用......如果你打算在 WHERE 子句等中使用列,你会想要对它们进行索引,而 JSON 模式会禁止你这样做。
1赞 Grigori Melnik 11/2/2019 #7

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