2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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 命令都能在继承层次结构上工作。用于数据查询、数据修改或模式修改的命令(例如,SELECTUPDATEDELETE、大多数 ALTER TABLE 的变体,但不包括 INSERTALTER TABLE ... RENAME)通常默认包含子表,并支持 ONLY 表示法来排除它们。大多数执行数据库维护和调优的命令(例如,REINDEX)仅在单个物理表上工作,并且不支持遍历继承层次结构。VACUUMANALYZE 命令默认包含子表,并且支持 ONLY 表示法以允许它们被排除。每个命令的具体行为在其参考页(SQL 命令)中有记录。

继承特性的一个严重限制是,索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于它们的继承子表。在引用方和被引用方外键约束上都是如此。因此,根据上面的例子

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

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

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

为继承层次结构未实现的一些功能已在声明式分区中实现。在决定是否为应用程序使用遗留继承分区时需要非常谨慎。

提交更正

如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。