On this page
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
以下的任何表。我们已经讨论过的许多命令SELECT
,UPDATE
和DELETE
支持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;
继承不会自动将数据从INSERT
或COPY
命令传播到继承层次结构中的其他表。在我们的示例中,以下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 TABLE的INHERIT
变体添加新的父关系。为此,新的子表必须已经包含名称和类型与父表相同的列。它还必须包括与父项具有相同名称和校验表达式的校验约束。同样,可以使用ALTER TABLE
的NO INHERIT
变体从子级中删除继承链接。当将继承关系用于表分区时,像这样动态添加和删除继承链接会很有用(请参见Section 5.10)。
创建兼容表(以后将成为新子表)的一种便捷方法是使用CREATE TABLE
中的LIKE
子句。这将创建一个与源表具有相同列的新表。如果在源表上定义了CHECK
约束,则应指定LIKE
的INCLUDING CONSTRAINTS
选项,因为新子级必须具有与父级匹配的约束,才能被认为是兼容的。
在保留任何子表的同时,不能删除父表。如果子表是从任何父表继承的,则子表的列或检查约束都不能删除或更改。如果要删除表及其所有后代,一种简单的方法是使用CASCADE
选项删除父表(请参阅Section 5.13)。
ALTER TABLE将传播列数据定义中的所有更改,并沿继承层次结构检查约束。同样,仅当使用CASCADE
选项时,才可以删除其他表所依赖的列。 ALTER TABLE
遵循在CREATE TABLE
期间适用于重复列合并和拒绝的相同规则。
继承的查询仅对父表执行访问权限检查。因此,例如,授予对cities
表的UPDATE
权限意味着通过cities
访问它们时也可以更新capitals
表中的行。这样可以保留数据也位于父表中的外观。但是capitals
表不能在没有其他授权的情况下直接更新。 TRUNCATE
和LOCK TABLE
是此规则的两个 exception,其中始终检查子表的权限,无论是直接处理还是通过对父表执行的命令递归处理它们。
以类似的方式,在继承的查询期间,父表的行安全策略(请参见Section 5.7)应用于子表中的行。子表的策略(如果有)仅在查询中显式命名的表时才应用;在这种情况下,将忽略附加到其父级的任何策略。
就像常规表一样,外部表(请参见Section 5.11)也可以作为父表或子表作为继承层次结构的一部分。如果外部表是继承层次结构的一部分,则整个层次结构也不支持外部表不支持的任何操作。
5.9.1. Caveats
请注意,并非所有的 SQL 命令都可以在继承层次结构上工作。用于数据查询,数据修改或架构修改的命令(例如SELECT
,UPDATE
,DELETE
,ALTER TABLE
的大多数变体,但不包括INSERT
或ALTER TABLE ... RENAME
)通常默认包含子表并支持ONLY
表示法将其排除在外。执行数据库维护和调整的命令(例如REINDEX
,VACUUM
)通常仅在单个物理表上起作用,并且不支持在继承层次结构上递归。每个单独命令的各自行为记录在其参考页(SQL Commands)中。
继承功能的一个严重限制是索引(包括唯一约束)和外键约束仅适用于单个表,不适用于它们的继承子级。在外键约束的引用和引用方面均是如此。因此,就以上示例而言:
如果我们声明
cities
。name
为UNIQUE
或PRIMARY KEY
,这不会阻止capitals
表具有名称与cities
中的行重复的行。默认情况下,那些重复的行将显示在cities
的查询中。实际上,默认情况下capitals
根本没有唯一约束,因此可以包含多个具有相同名称的行。您可以向capitals
添加唯一约束,但是与cities
相比,这不能防止重复。同样,如果我们要指定
cities
。name
REFERENCES
其他表,此约束不会自动传播到capitals
。在这种情况下,您可以通过将相同的REFERENCES
约束手动添加到capitals
来解决它。指定另一个表的列
REFERENCES cities(name)
将允许另一个表包含城市名称,但不能包含大写名称。在这种情况下,没有好的解决方法。
未为继承层次结构实现的某些功能是为声明性分区实现的。在确定具有旧式继承的分区对您的应用程序是否有用时,需要格外小心。