5.9. Inheritance

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

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

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

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

在这种情况下,capitals表“继承”了其父表cities的所有列。State 首府也有一个额外的列state,显示其 State 名。

在 PostgreSQL 中,一个表可以继承零个或多个其他表,而查询可以引用一个表的所有行或一个表的所有行及其所有后代表。后一种行为是默认行为。例如,以下查询查找海拔超过 500 英尺的所有城市(包括 State 首府)的名称:

SELECT name, elevation
    FROM cities
    WHERE elevation > 500;

给定 PostgreSQL 教程的示例数据(请参阅Section 2.1),将返回:

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

另一方面,以下查询将查找非 State 首府且海拔超过 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;

which returns:

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;

which returns:

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始终完全插入指定的表中。在某些情况下,可以使用规则重定向插入(请参见Chapter 41)。但是,这对上述情况没有帮助,因为cities表不包含state列,因此在应用规则之前,该命令将被拒绝。

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

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

通常在创建子表时使用CREATE TABLE语句的INHERITS子句构建表继承。另外,已经使用兼容方式定义的表可以使用ALTER TABLEINHERIT变体添加新的父关系。为此,新的子表必须已经包含名称和类型与父表相同的列。它还必须包括与父项具有相同名称和校验表达式的校验约束。同样,可以使用ALTER TABLENO INHERIT变体从子级中删除继承链接。当将继承关系用于表分区时,像这样动态添加和删除继承链接会很有用(请参见Section 5.10)。

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

在保留任何子表的同时,不能删除父表。如果子表是从任何父表继承的,则子表的列或检查约束都不能删除或更改。如果要删除表及其所有后代,一种简单的方法是使用CASCADE选项删除父表(请参阅Section 5.13)。

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

继承的查询仅对父表执行访问权限检查。因此,例如,授予对cities表的UPDATE权限意味着通过cities访问它们时也可以更新capitals表中的行。这样可以保留数据也位于父表中的外观。但是capitals表不能在没有其他授权的情况下直接更新。 TRUNCATELOCK TABLE是此规则的两个 exception,其中始终检查子表的权限,无论是直接处理还是通过对父表执行的命令递归处理它们。

以类似的方式,在继承的查询期间,父表的行安全策略(请参见Section 5.7)应用于子表中的行。子表的策略(如果有)仅在查询中显式命名的表时才应用;在这种情况下,将忽略附加到其父级的任何策略。

就像常规表一样,外部表(请参见Section 5.11)也可以作为父表或子表作为继承层次结构的一部分。如果外部表是继承层次结构的一部分,则整个层次结构也不支持外部表不支持的任何操作。

5.9.1. Caveats

请注意,并非所有的 SQL 命令都可以在继承层次结构上工作。用于数据查询,数据修改或架构修改的命令(例如SELECTUPDATEDELETEALTER TABLE的大多数变体,但不包括INSERTALTER TABLE ... RENAME)通常默认包含子表并支持ONLY表示法将其排除在外。执行数据库维护和调整的命令(例如REINDEXVACUUM)通常仅在单个物理表上起作用,并且不支持在继承层次结构上递归。每个单独命令的各自行为记录在其参考页(SQL Commands)中。

继承功能的一个严重限制是索引(包括唯一约束)和外键约束仅适用于单个表,不适用于它们的继承子级。在外键约束的引用和引用方面均是如此。因此,就以上示例而言:

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

  • 同样,如果我们要指定citiesname REFERENCES其他表,此约束不会自动传播到capitals。在这种情况下,您可以通过将相同的REFERENCES约束手动添加到capitals来解决它。

  • 指定另一个表的列REFERENCES cities(name)将允许另一个表包含城市名称,但不能包含大写名称。在这种情况下,没有好的解决方法。

未为继承层次结构实现的某些功能是为声明性分区实现的。在确定具有旧式继承的分区对您的应用程序是否有用时,需要格外小心。