使用表继承代替映射表

Dmi*_*tri 5 postgresql database-design inheritance subtypes

这似乎是一个很常见的场景:几种类型都构成相同的子类型。

这通常看起来像这样:

-- 'name' is unique per parent record
CREATE TABLE sometype (
  sometype_id serial PRIMARY KEY,
  name        text
);

CREATE TABLE foo (foo_id serial PK);
CREATE TABLE bar (bar_id serial PK);

CREATE TABLE foo_sometype (
  foo_id      int4,
  sometype_id int4
);

CREATE TABLE bar_sometype (
  bar_id      int4,
  sometype_id int4
);
Run Code Online (Sandbox Code Playgroud)

这很好,但查询起来很麻烦。我认为这可能更清洁:

-- 'name' is unique per parent record
CREATE TABLE sometype (
  name        text
);

CREATE TABLE foo_sometype (
  foo_id      int4
) INHERITS(sometype);

CREATE TABLE bar_sometype (
  bar_id      int4,
) INHERITS(sometype);
Run Code Online (Sandbox Code Playgroud)

我喜欢这个:

  • 加入简单(与USING
  • 无需向“sometype”添加代理键,它明确是“foo”和“bar”的组成部分

不过,这似乎是继承的一种非典型用法。

有什么理由这样做?


论继承的“不足”

请注意,Pg 继承的标准警告仅在使用表继承直接建模类继承时才相关,这不是我在这里所做的。事实上,为了让它工作,我需要继承来表现它的行为方式。

我几乎希望他们将其称为“继承”以外的东西,因为这种行为非常合乎逻辑,而“缺点”仅与一个用例相关。


优于手动复制表结构

正如 Evan 指出的那样,我可以手动创建与我描述的完全一样的 'foo_sometype' 和 'bar_sometype',但我认为继承结构有几个显着的好处:

  • 继承关系明确地将 'foo_sometype' 和 'bar_sometype' 定义为相同类型,而不仅仅是两个碰巧具有相同列的表。
  • 通过父表进行未来的架构更改会减少意外分歧的机会(只需做一点工作,这实际上可以强制执行)。
  • 更重要的是,可以针对父表生成客户端代码,并且只需更改表名即可将其应用于子表,(再次)确信表结构是强制执行的。

因此,作为一个人为的例子,Foo 和 Bar 可能有一个 HasSomeTypeList 特征,它抽象了所有“sometype”操作,并且知道两个表都可以映射到 SomeType 类。

表示 Foo/Bar 关系,无论是建模为特征还是继承,都是这里的最终目标。

顺便说一句,对于天真的用户/查询编写者——他们不希望进行架构更改——这两种方式看起来是一样的。

Eva*_*oll 9

继承是我不会接触的功能之一。AFAIK,它在内部用于某些容量的复制和分区。我不确定它是否是为了最终用户使用而设计的。

混凝土技术缺陷

UNIQUE 和 REFERENCES 的缺点

文档涵盖了CAVEAT 部分中的一些缺点(下面很重要)。

  • 如果我们将 parent.name 声明为 UNIQUE 或 PRIMARY KEY,这不会阻止子表的行名称与父表中的行重复。默认情况下,这些重复的行会显示在来自父级的查询中。事实上,默认情况下 child 根本没有唯一约束,因此可以包含多个具有相同名称的行。您可以向子项添加唯一约束,但与父项相比,这不会防止重复。
  • 类似地,如果我们将 parent.name REFERENCES 指定为其他某个表,则此约束不会自动传播到 child。在这种情况下,您可以通过将相同的 REFERENCES 约束手动添加到 child 来解决它。
  • 指定另一个表的列 REFERENCES parent(name) 将允许另一个表包含父名称,但不能包含子名称。对于这种情况,没有好的解决方法。

开发 INHERIT 的进展缓慢

这些缺陷首先在 1996 年发布的7.3 文档中提到,尽管它们在继承实施后就存在了

这个缺陷可能会在未来的某个版本中得到修复。

唯一的变化是使2010 年发布的8.0 文档中的缺陷更加明确和详细。

这些缺陷可能会在未来的某个版本中得到修复,但同时在决定继承是否对您的问题有用时需要相当小心。

祝你好运,等待未来的发布。而且,您谈论的某些功能并不是构图独有的,

保存“钥匙”没有意义

  • 'sometype' 上没有代理键,它显然是一个组合

这与制作sometype属性列表并直接链接到它有什么不同?

CREATE TABLE sometype (sometype_name text PRIMARY KEY);
CREATE TABLE foo (foo_id serial PRIMARY KEY);
CREATE TABLE foo_sometype (
  foo_id int REFERENCES foo,
  sometype_name text REFERENCES sometype,
  PRIMARY KEY ( foo_id, sometype_name )
);
Run Code Online (Sandbox Code Playgroud)

现在,你甚至不必加入foo_sometypesometype得到sometype.sometype_name

表分区

撇开所有这些问题不谈,即将发布的 PostgreSQL 10 表分区问题变得更糟

不允许多重继承,分区和继承不能混用

那你要继承吗?放弃分区,它实际上具有真正的规划器优势。

更改表

唉,ALTER TABLE它的注释中也列出了很多缺点,

如果表有后代表,则不允许在父表中添加、重命名或更改列的类型,或重命名继承的约束而不对后代进行相同的操作。也就是说,ALTER TABLE ONLY 将被拒绝。这可确保后代始终具有与父项匹配的列。[...] 递归 DROP COLUMN 操作将删除后代表的列,仅当后代不从任何其他父项继承该列并且从未对该列进行独立定义时。非递归 DROP COLUMN(即 ALTER TABLE ONLY ... DROP COLUMN)永远不会删除任何后代列,而是将它们标记为独立定义而不是继承。[...] TRIGGER、CLUSTER、OWNER 和 TABLESPACE 操作永远不会递归到后代表;那是,他们总是表现得好像只被指定了一样。仅对未标记为 NO INHERIT 的 CHECK 约束递归添加约束。

结论

我认为没有多少人使用继承。我从未在野外见过它。db 中的继承增加了学习曲线,一些功能最好单独保留。您不必为它们找到应用程序。

您可能会发现Stack Overflow 上的这篇文章很有用,“何时在 PostgreSQL 中使用继承的表?”。


小智 6

提到的缺陷不是不使用继承的理由!继承在这里的工作类似于具有独立对象的类继承。您可能有一个类/表“水果”和一个类/表“苹果”和“橙子”。由于苹果和橙子从水果继承了它们的元定义,因此您可以通过水果获取它们。然而,它们是独立的类,具有独立的枚举,还有什么你可以期待......

如果您确实需要防止冲突:在主表上定义触发器或检查/外键(不包含)。

但是请不要因为您对 PostgreSQL 中继承的工作方式不满意而阻止人们使用它!独立的子类或表都很棒!如果你需要一些依赖 - 以你需要的方式实现它。有很多很好的教程,包括官方文档。这是另一个例子:继承——爱 PostgreSQL 的另一个理由

尽管可能存在主键冲突(我的父表没有定义主键,而且我的模型不需要它),但我也在我的一个项目中使用了继承。

还有一些方法可以使用继承来提高性能,而不仅仅是 ORM。

难不等于坏。