支持的版本: 当前 (17) / 16 / 15 / 14 / 13
开发版本: 开发版
不支持的版本: 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3

5.11. 继承 #

PostgreSQL 实现了表继承,这对于数据库设计人员来说可能是一个有用的工具。(SQL:1999 及更高版本定义了一种类型继承功能,它在许多方面与此处描述的功能不同。)

让我们从一个例子开始:假设我们正在尝试构建一个城市的数据模型。每个州都有许多城市,但只有一个首府。我们希望能够快速检索任何特定州的首府。这可以通过创建两个表来完成,一个用于州首府,另一个用于不是首府的城市。但是,当我们想要询问有关城市的数据时,无论它是否是首府,会发生什么?继承功能可以帮助解决这个问题。我们定义了 capitals 表,使其继承自 cities

CREATE TABLE cities (
    name            text,
    population      float,
    elevation       int     -- in feet
);

CREATE TABLE capitals (
    state           char(2)
) INHERITS (cities);

在这种情况下,capitals继承其父表 cities 的所有列。州首府还有一个额外的列 state,显示它们所在的州。

PostgreSQL 中,一个表可以从零个或多个其他表继承,并且一个查询可以引用一个表的所有行,也可以引用一个表的所有行加上其所有后代表。后一种行为是默认行为。例如,以下查询查找所有城市(包括州首府)的名称,这些城市的海拔高度超过 500 英尺

SELECT name, elevation
    FROM cities
    WHERE elevation > 500;

给定 PostgreSQL 教程中的示例数据(请参阅 第 2.1 节),这将返回

   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953
 Madison   |       845

另一方面,以下查询查找所有不是州首府并且海拔高度超过 500 英尺的城市

SELECT name, elevation
    FROM ONLY cities
    WHERE elevation > 500;

   name    | elevation
-----------+-----------
 Las Vegas |      2174
 Mariposa  |      1953

这里的 ONLY 关键字表示该查询应仅应用于 cities,而不应用于继承层次结构中 cities 下的任何表。我们已经讨论过的许多命令 — SELECTUPDATEDELETE — 都支持 ONLY 关键字。

您还可以使用尾随的 * 编写表名,以明确指定包含后代表

SELECT name, elevation
    FROM cities*
    WHERE elevation > 500;

编写 * 不是必需的,因为此行为始终是默认行为。但是,为了与可以更改默认值的旧版本兼容,仍然支持此语法。

在某些情况下,您可能希望知道特定行来自哪个表。每个表中都有一个名为 tableoid 的系统列,它可以告诉您原始表

SELECT c.tableoid, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;

这将返回

 tableoid |   name    | elevation
----------+-----------+-----------
   139793 | Las Vegas |      2174
   139793 | Mariposa  |      1953
   139798 | Madison   |       845

(如果您尝试重现此示例,您可能会得到不同的数字 OID。)通过与 pg_class 进行连接,您可以看到实际的表名

SELECT p.relname, c.name, c.elevation
FROM cities c, pg_class p
WHERE c.elevation > 500 AND c.tableoid = p.oid;

这将返回

 relname  |   name    | elevation
----------+-----------+-----------
 cities   | Las Vegas |      2174
 cities   | Mariposa  |      1953
 capitals | Madison   |       845

获得相同效果的另一种方法是使用 regclass 别名类型,它将以符号方式打印表 OID

SELECT c.tableoid::regclass, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;

继承不会自动将 INSERTCOPY 命令中的数据传播到继承层次结构中的其他表。在我们的示例中,以下 INSERT 语句将失败

INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');

我们可能希望数据以某种方式路由到 capitals 表,但这不会发生:INSERT 始终插入到指定的表中。在某些情况下,可以使用规则重定向插入(请参阅 第 39 章)。但是,这对于上述情况没有帮助,因为 cities 表不包含列 state,因此该命令将在应用规则之前被拒绝。

父表上的所有检查约束和非空约束都会自动被其子表继承,除非使用 NO INHERIT 子句明确指定其他情况。其他类型的约束(唯一约束、主键约束和外键约束)不会被继承。

一个表可以从多个父表继承,在这种情况下,它具有父表定义的列的并集。在子表的定义中声明的任何列都会添加到这些列中。如果同一个列名出现在多个父表中,或者同时出现在父表和子表的定义中,则这些列将被合并,以便子表中只有一个这样的列。要合并,列必须具有相同的数据类型,否则会引发错误。可继承的检查约束和非空约束以类似的方式合并。因此,例如,如果它来自的任何一个列定义都被标记为非空,则合并的列将被标记为非空。如果检查约束具有相同的名称,则会合并它们,如果它们的条件不同,则合并将失败。

通常,当创建子表时,使用 CREATE TABLE 语句的 INHERITS 子句来建立表继承。或者,可以使用 ALTER TABLEINHERIT 变体添加新的父关系,到一个已以兼容方式定义的表。为此,新的子表必须已经包含与父表的列具有相同名称和类型的列。它还必须包含与父表的约束具有相同名称和检查表达式的检查约束。类似地,可以使用 ALTER TABLENO INHERIT 变体从子表中删除继承链接。当继承关系用于表分区时,像这样动态地添加和删除继承链接可能很有用(请参阅 第 5.12 节)。

创建稍后将成为新子表的兼容表的一种便捷方法是使用 CREATE TABLE 中的 LIKE 子句。这将创建一个与源表具有相同列的新表。如果在源表上定义了任何 CHECK 约束,则应指定 LIKEINCLUDING CONSTRAINTS 选项,因为新的子表必须具有与父表匹配的约束才能被认为是兼容的。

当其任何子表仍然存在时,不能删除父表。如果子表的列或检查约束是从任何父表继承的,也不能删除或更改它们。如果您希望删除一个表及其所有后代,一种简单的方法是使用 CASCADE 选项删除父表(请参阅 第 5.15 节)。

ALTER TABLE 会将列数据定义和检查约束中的任何更改向下传播到继承层次结构中。同样,仅当使用 CASCADE 选项时,才能删除其他表依赖的列。ALTER TABLE 遵循在 CREATE TABLE 期间应用的重复列合并和拒绝的相同规则。

继承的查询仅对父表执行访问权限检查。因此,例如,在 cities 表上授予 UPDATE 权限意味着当通过 cities 访问时,也具有更新 capitals 表中行的权限。这保留了数据(也)在父表中的外观。但是,如果没有额外的授予,则无法直接更新 capitals 表。类似地,父表的行安全策略(请参阅 第 5.9 节)在继承查询期间应用于来自子表的行。子表的策略(如果有)仅在它是查询中显式命名的表时才应用;在这种情况下,附加到其父表的任何策略都将被忽略。

外部表(请参阅第 5.13 节)也可以作为继承层次结构的一部分,既可以作为父表,也可以作为子表,就像常规表一样。如果外部表是继承层次结构的一部分,则外部表不支持的任何操作在该整个层次结构上也不支持。

5.11.1. 注意事项 #

请注意,并非所有SQL命令都能在继承层次结构上工作。用于数据查询、数据修改或模式修改的命令(例如,SELECTUPDATEDELETEALTER TABLE的大多数变体,但不包括INSERTALTER TABLE ... RENAME)通常默认包括子表,并支持ONLY标记来排除它们。用于数据库维护和调优的命令(例如,REINDEXVACUUM)通常只对单个物理表起作用,并且不支持递归遍历继承层次结构。每个命令的各自行为在其参考页面(SQL 命令)中都有说明。

继承功能的一个严重限制是,索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于其继承的子表。这在外键约束的引用端和被引用端都是如此。因此,在上面的示例中

  • 如果我们声明cities.nameUNIQUEPRIMARY KEY,这将不会阻止capitals表拥有与cities表中重复的名称的行。 并且这些重复的行默认会出现在对cities的查询中。实际上,默认情况下capitals根本没有任何唯一约束,因此可能包含多个具有相同名称的行。 您可以向capitals添加唯一约束,但这并不能防止与cities相比的重复。

  • 同样,如果我们指定cities.name REFERENCES其他某个表,则此约束不会自动传播到capitals。在这种情况下,您可以通过手动向capitals添加相同的REFERENCES约束来解决此问题。

  • 指定另一个表的列REFERENCES cities(name)将允许该其他表包含城市名称,但不包含首都名称。 对于这种情况,没有很好的解决方法。

某些未为继承层次结构实现的功能已为声明式分区实现。 在决定使用遗留继承进行分区是否对您的应用程序有用时,需要格外小心。

提交更正

如果您发现文档中存在任何不正确、与您在特定功能上的体验不符或需要进一步澄清的地方,请使用此表单报告文档问题。