Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

5.11. 继承 #

5.11.1. 注意事项

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 下的任何表。我们已经讨论过的许多命令 — SELECTUPDATEDELETE — 支持 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;

继承不会自动将数据从 INSERTCOPY 命令传播到继承层次结构中的其他表。在我们的示例中,下面的 INSERT 语句会失败。

INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');

我们可能希望数据能够通过某种方式被路由到 capitals 表,但这种情况不会发生:INSERT 总是准确地插入到指定表中。在某些情况下,可以使用规则(请参见 第 39 章)来重定向插入。但那对于上述情况没有帮助,因为 cities 表不包含列 state,所以该命令将在该规则应用之前被拒绝。

父表上的所有检查约束和非空约束都会自动由子表继承,除非明确使用含有 NO INHERIT 子句来另外指定。其他类型的约束(唯一、主键和外键约束)不会被继承。

一个表可以从多个父表中继承,在这种情况下,它具有由父表中定义的列的并集。在子表的定义中声明的任何列将被添加到其中。如果在多个父表中或在父表和子表的定义中都出现了相同的列名称,则这些列将被 合并,以便子表中只存在一个这样的列。为了合并,列必须具有相同的数据类型,否则会引发错误。可继承的检查约束和非空约束的合并方式与此类似。因此,例如,如果某个列定义是标记为非空的,那么合并的列将被标记为非空。检查约束在具有相同名称的情况下会合并,如果它们的条件不同,那么合并将失败。

通常在创建子表时建立表继承,使用 CREATE TABLE 语句的 INHERITS 子句。或者,可以通过使用 ALTER TABLEINHERIT 变体,为已经以兼容方式定义的表添加新的父关系。为此,新的子表必须已经包含与父表的列名称和类型相同的列。它还必须包含与父表相同的名称和检查表达式的检查约束。同样,可以使用 ALTER TABLENO INHERIT 变体从子表中删除继承链接。像这样动态添加和删除继承链接在表分区中使用继承关系时很有用(见 第 5.12 节)。

创建将成为新子的兼容表的一种便捷方法是在 CREATE TABLE 中使用 LIKE 子句。这将创建一个与源表具有相同列的新表。如果源表上定义了任何 CHECK 约束,则应指定 LIKEINCLUDING CONSTRAINTS 选项,因为新的子必须具有与父匹配的约束才能被视为兼容。

当父表的任何子表仍然存在时,无法删除父表。同样,如果子表从父表继承,则子表的列或检查约束也无法删除或修改。如果您想删除表及其所有后代,一种简单的方法是使用 CASCADE 选项删除父表(见 第 5.15 节)。

ALTER TABLE 将传播继承层次中的列数据定义和检查约束中的任何更改。同样,只有在使用 CASCADE 选项时,才有可能删除其他表依赖的列。 ALTER TABLE 遵循与 CREATE TABLE 期间应用的重复列合并和拒绝相同的规则。

继承查询仅对父表执行访问权限检查。因此,例如,在 cities 表格上授予 UPDATE 权限表示也有权更新 capitals 表格中的行,当通过 cities 访问它们时。这保留了数据(也)在父表中的表象。但在没有其他授予的情况下,不能直接更新 capitals 表格。以类似的方式,父表格的行安全策略(见 第 5.9 节)在继承查询期间应用于来自子表格的行。子表格的策略(如果有)仅在查询中明确命名该表格时才应用;在这种情况下,将忽略所有附加到其父表格的策略。

外表格(见 第 5.13 节)也可以作为继承层次结构的一部分,作为父表格或子表格,就像常规表格一样。如果外表格是继承层次结构的一部分,那么外表格不支持的任何操作也不支持整个层次结构。

5.11.1. 注意事项 #

请注意,并非所有 SQL 命令都能处理继承层次结构。用于数据查询、数据修改或架构修改的命令(例如,SELECTUPDATEDELETE、大多数 ALTER TABLE 变体,但不是 INSERTALTER TABLE ... RENAME)通常默认包括子表格,并支持 ONLY 符号将它们排除在外。用于数据库维护和调优的命令(例如,REINDEXVACUUM)通常仅适用于单个的、物理表格,并且不支持递归遍历继承层次结构。每个单独命令的相应行为在其参考页面(SQL 命令)中都有记录。

继承特性的一个严重限制是索引(包括唯一性约束)和外键约束仅适用于单个表格,不适用于其继承子项。无论在外键约束的引用方还是被引用方上都是如此。因此,在上面示例中

  • 如果声明 cities.nameUNIQUEPRIMARY KEY,这并不会阻止 capitals 表拥有与 cities 中行重复的名称的行。并且默认情况下,这些重复行将显示在 cities 的查询中。事实上,默认情况下,capitals 根本没有任何唯一性约束,因此可能包含具有相同名称的多行。您可以向 capitals 添加唯一性约束,但这并不能防止与 cities 相比的重复。

  • 同样,如果我们要指定 cities.name REFERENCES 某个其他表,则此约束不会自动传播到 capitals。在这种情况下,您可以通过手动向 capitals 添加相同的 REFERENCES 约束来解决此问题。

  • 指定另一个表的列 REFERENCES cities(name) 将允许该表包含城市名称,但不能包含首都名称。对于这种情况,没有好的解决办法。

没有针对继承层次结构实现的部分功能已针对声明式分区实现。在决定是否将分区与传统继承用于您的应用程序时,需要格外小心。