5.10. 继承#
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
下面的任何表。我们已经讨论过的许多命令(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;
它返回
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;
继承不会自动将数据从INSERT
或COPY
命令传播到继承层次结构中的其他表。在我们的示例中,以下INSERT
语句将失败
INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');
我们可能希望数据以某种方式路由到capitals
表,但不会发生这种情况:INSERT
始终插入到指定的表中。在某些情况下,可以使用规则重定向插入(请参阅第 41 章)。但是,对于上述情况,这没有帮助,因为cities
表不包含列state
,因此在应用规则之前,该命令将被拒绝。
父表上的所有检查约束和非空约束都会自动继承给其子表,除非使用NO INHERIT
子句明确指定了其他方式。其他类型的约束(唯一、主键和外键约束)不会被继承。
一个表可以从多个父表继承,在这种情况下,它具有父表定义的列的并集。在子表的定义中声明的任何列都会被添加到这些列中。如果同一列名出现在多个父表中,或者同时出现在父表和子表的定义中,那么这些列将“合并”,以便在子表中只有一个这样的列。要合并,列必须具有相同的数据类型,否则会引发错误。可继承的检查约束和非空约束以类似的方式合并。因此,例如,如果合并列中任何一个列定义标记为非空,则合并列将标记为非空。如果检查约束具有相同的名称,则会合并这些约束,如果它们的条件不同,则合并将失败。
表继承通常在创建子表时建立,使用CREATE TABLE
语句的INHERITS
子句。或者,可以使用ALTER TABLE
的INHERIT
变体,为已经以兼容方式定义的表添加新的父关系。为此,新的子表必须已经包含与父表的列具有相同名称和类型的列。它还必须包含与父表具有相同名称和检查表达式的检查约束。同样,可以使用ALTER TABLE
的NO INHERIT
变体从子表中删除继承链接。当继承关系用于表分区时,动态添加和删除此类继承链接非常有用(请参阅第 5.11 节)。
创建兼容表(稍后将成为新的子表)的一种便捷方法是在CREATE TABLE
中使用LIKE
子句。这将创建一个与源表具有相同列的新表。如果源表上定义了任何CHECK
约束,则应指定LIKE
的INCLUDING CONSTRAINTS
选项,因为新的子表必须具有与父表匹配的约束才能被视为兼容。
在任何子表存在时,父表都无法被删除。如果子表的列或检查约束是从任何父表继承的,则也无法删除或更改它们。如果您希望删除一个表及其所有后代,一种简单的方法是使用CASCADE
选项删除父表(请参见第 5.14 节)。
ALTER TABLE
将传播列数据定义和检查约束中的任何更改,一直到继承层次结构的底部。同样,只有在使用CASCADE
选项时,才有可能删除其他表依赖的列。ALTER TABLE
遵循与CREATE TABLE
期间应用的重复列合并和拒绝相同的规则。
继承查询仅对父表执行访问权限检查。因此,例如,授予cities
表的UPDATE
权限意味着在通过cities
访问时,也有权更新capitals
表中的行。这保留了数据(也)在父表中的表象。但是,如果没有额外的授予,则无法直接更新capitals
表。以类似的方式,父表的行安全策略(请参见第 5.8 节)在继承查询期间应用于来自子表的行。子表的策略(如果有)仅在其在查询中明确命名时应用;在这种情况下,将忽略附加到其父表的任何策略。
外部表(请参见第 5.12 节)也可以是继承层次结构的一部分,既可以作为父表,也可以作为子表,就像常规表一样。如果外部表是继承层次结构的一部分,那么外部表不支持的任何操作在整个层次结构中也不支持。
5.10.1. 注意事项#
请注意,并非所有 SQL 命令都能处理继承层次结构。用于数据查询、数据修改或模式修改的命令(例如,SELECT
、UPDATE
、DELETE
、大多数ALTER TABLE
变体,但不包括INSERT
或ALTER TABLE ... RENAME
)通常默认为包括子表并支持ONLY
表示法以排除它们。用于执行数据库维护和调优的命令(例如,REINDEX
、VACUUM
)通常仅适用于单个物理表,并且不支持在继承层次结构中递归。每条命令的相应行为在其参考页(SQL 命令)中都有记录。
继承功能的一个严重限制是,索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于其继承子项。这在引用外键约束的引用方和被引用方都成立。因此,根据上述示例
如果我们声明
cities
.name
为UNIQUE
或PRIMARY KEY
,这不会阻止capitals
表中存在与cities
中的行重复的名称的行。并且这些重复的行默认情况下会显示在cities
的查询中。事实上,默认情况下capitals
根本没有唯一约束,因此可能包含具有相同名称的多行。你可以向capitals
添加唯一约束,但这并不会阻止与cities
相比的重复。类似地,如果我们要指定
cities
.name
REFERENCES
某个其他表,此约束不会自动传播到capitals
。在这种情况下,你可以通过手动向capitals
添加相同的REFERENCES
约束来解决此问题。指定另一个表的列
REFERENCES cities(name)
将允许另一个表包含城市名称,但不包含首都名称。对于这种情况,没有好的解决方法。
一些未为继承层次结构实现的功能已为声明式分区实现。在决定使用传统继承进行分区是否对你的应用程序有用时,需要格外小心。