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
选项时,才能删除其他表所依赖的列。在 CREATE TABLE
期间适用的重复列合并和拒绝规则,ALTER TABLE
也遵循相同的规则。
继承的查询仅在父表上执行访问权限检查。因此,例如,授予 cities
表的 UPDATE
权限意味着还允许更新 capitals
表中的行,当它们通过 cities
访问时。这保留了数据也在父表中的外观。但是,capitals
表不能在没有额外授权的情况下直接更新。以类似的方式,父表的行安全策略(参见 第 5.9 节)应用于在继承查询期间来自子表的行。子表的策略(如果有)仅在它是查询中显式命名的表时才应用;在这种情况下,任何附加到其父表的策略都将被忽略。
外表(参见 第 5.13 节)也可以作为继承层次结构的一部分,作为父表或子表,就像普通表一样。如果外表是继承层次结构的一部分,那么外表不支持的任何操作也不支持整个层次结构。
请注意,并非所有 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)
将允许另一个表包含城市名称,但不包含首都名称。这种情况没有好的解决方法。
一些没有为继承层次结构实现的功能已为声明式分区实现。在决定分区与传统继承是否对你的应用程序有用时,需要格外小心。
如果你在文档中发现任何不正确的内容,与你对特定功能的体验不符,或需要进一步澄清,请使用 此表格 报告文档问题。