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
权限表示也有权更新 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)
将允许该表包含城市名称,但不能包含首都名称。对于这种情况,没有好的解决办法。
没有针对继承层次结构实现的部分功能已针对声明式分区实现。在决定是否将分区与传统继承用于您的应用程序时,需要格外小心。