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
总是精确地插入到指定的表中。在某些情况下,可以使用规则(请参见 第 39 章)来重定向插入。但是,这对于上述情况没有帮助,因为 cities
表不包含 state
列,因此在规则应用之前命令就会被拒绝。
父表上的所有检查约束和非空约束都会被其子表自动继承,除非使用 NO INHERIT
子句明确指定。其他类型的约束(唯一约束、主键约束和外键约束)不会被继承。
一个表可以继承自多个父表,在这种情况下,它拥有所有父表定义的列的并集。子表定义中声明的任何列都会添加到这些列中。如果同一列名出现在多个父表中,或者出现在父表和子表的定义中,则这些列会被“合并”,以便子表中只有一个这样的列。要合并,列必须具有相同的数据类型,否则会引发错误。可继承的检查约束和非空约束也会以类似的方式合并。因此,例如,如果它来自的某个列定义被标记为非空,那么合并后的列将被标记为非空。如果检查约束具有相同的名称,它们就会被合并,如果它们的条件不同,合并将失败。
表继承通常在创建子表时建立,使用 CREATE TABLE
语句的 INHERITS
子句。或者,可以使用 ALTER TABLE
的 INHERIT
变体,将一个已定义的兼容表添加新的父表关系。为此,新子表必须已经包含与父表列名称和类型相同的列。它还必须包含与父表具有相同名称和检查表达式的检查约束。类似地,可以使用 ALTER TABLE
的 NO INHERIT
变体从子表中删除继承链接。像这样动态添加和删除继承链接可能很有用,当继承关系用于表分区时(请参见 第 5.12 节)。
创建兼容表以便以后成为新子表的一种便捷方法是在 CREATE TABLE
中使用 LIKE
子句。这会创建一个具有与源表相同列的新表。如果源表上定义了任何 CHECK
约束,则应指定 LIKE
的 INCLUDING CONSTRAINTS
选项,因为新子表必须具有与父表兼容的约束才能被视为兼容。
在任何子表仍然存在的情况下,无法删除父表。也不能删除或更改子表的列或检查约束,如果它们是从任何父表继承的。如果您想删除一个表及其所有后代,一种简单的方法是使用 CASCADE
选项删除父表(请参见 第 5.15 节)。
ALTER TABLE
会将列数据定义和检查约束的任何更改向下传播到继承层次结构。同样,只有在使用 CASCADE
选项时,才能删除被其他表依赖的列。ALTER TABLE
遵循与 CREATE TABLE
相同的重复列合并和拒绝规则。
继承查询仅对父表执行访问权限检查。因此,例如,授予对 cities
表的 UPDATE
权限,意味着当通过 cities
访问 capitals
表中的行时,也具有更新这些行的权限。这保留了数据(也)位于父表中的外观。但是,如果没有额外的授权,则无法直接更新 capitals
表。以类似的方式,在继承查询中,父表的行安全策略(请参见 第 5.9 节)适用于来自子表的行。子表的策略(如果存在)仅在它作为查询中明确命名的表时应用;在这种情况下,其父表上的任何策略都会被忽略。
外部表(请参见 第 5.13 节)也可以是继承层次结构的一部分,无论是作为父表还是子表,就像常规表一样。如果外部表是继承层次结构的一部分,那么任何外部表不支持的操作在整个层次结构上也不支持。
请注意,并非所有 SQL 命令都能在继承层次结构上工作。用于数据查询、数据修改或模式修改的命令(例如,SELECT
、UPDATE
、DELETE
、大多数 ALTER TABLE
的变体,但不包括 INSERT
或 ALTER TABLE ... RENAME
)通常默认包含子表,并支持 ONLY
表示法来排除它们。大多数执行数据库维护和调优的命令(例如,REINDEX
)仅在单个物理表上工作,并且不支持遍历继承层次结构。VACUUM
和 ANALYZE
命令默认包含子表,并且支持 ONLY
表示法以允许它们被排除。每个命令的具体行为在其参考页(SQL 命令)中有记录。
继承特性的一个严重限制是,索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于它们的继承子表。在引用方和被引用方外键约束上都是如此。因此,根据上面的例子
如果我们声明 cities
.name
为 UNIQUE
或 PRIMARY KEY
,这不会阻止 capitals
表中出现与 cities
表中行重复的名称。而且,默认情况下,这些重复的行会出现在从 cities
发起的查询中。实际上,默认情况下 capitals
将没有任何唯一约束,因此可能包含多个名称相同的行。您可以为 capitals
添加一个唯一约束,但这不会阻止与 cities
相比的重复。
同样,如果我们指定 cities
.name
REFERENCES
另一个表,此约束不会自动传播到 capitals
。在这种情况下,您可以通过手动为 capitals
添加相同的 REFERENCES
约束来解决。
指定另一个表的列 REFERENCES cities(name)
将允许另一个表包含城市名称,但不能包含首府名称。在这种情况下没有好的解决方法。
为继承层次结构未实现的一些功能已在声明式分区中实现。在决定是否为应用程序使用遗留继承分区时需要非常谨慎。
如果您在文档中看到任何不正确、与您对特定功能的体验不符或需要进一步说明的内容,请使用 此表单 报告文档问题。