继承

PostgreSQL 9.4.4 中文手册
上一页 | 上一级 | 章 5. 数据定义 | 下一页

# 5.8. 继承

PostgreSQL实现了表继承， 这个特性对数据库设计人员来说是一个很有效的工具。SQL99 及以后的标准定义了类型继承特性， 和我们在这里描述的很多特性有区别。

让我们从一个例子开始：假设我们试图制作一个城市数据模型。每个州都有许多城市， 但是只有一个首府。我们希望能够迅速检索任何州的首府。这个任务可以通过创建两个表来实现， 一个是州府表，一个是非州府表。不过，如果我们不管什么城市都想查该怎么办? 继承的特性可以帮助我们解决这个问题。我们定义`capitals`表， 它继承自`cities`表：

    CREATE TABLE cities (
        name            text,
        population      float,
        altitude        int     -- 英尺
    );
    CREATE TABLE capitals (
        state           char(2)
    ) INHERITS (cities);

在这种情况下，`capitals`表继承它的父表`cities` 中的所有属性。州首府有一个额外的`state`属性显示其所在的州。

在PostgreSQL里，一个表可以从零个或多个其它表中继承属性， 而且一个查询既可以引用一个表中的所有行， 也可以引用一个表及其所有后代表的行(后面这个是缺省行为)。比如， 下面的查询查找所有海拔 500 英尺以上的城市名，包括州首府：

    SELECT name, altitude
        FROM cities
        WHERE altitude > 500;

使用PostgreSQL教程里面的数据(参阅第 2.1 节)，它返回：

       name    | altitude
    -----------+----------
     Las Vegas |     2174
     Mariposa  |     1953
     Madison   |      845

另一方面，如果要找出不包括州首府的所有海拔超过 500 英尺的城市，查询应该是这样的：

    SELECT name, altitude
        FROM ONLY cities
        WHERE altitude > 500;

       name    | altitude
    -----------+----------
     Las Vegas |     2174
     Mariposa  |     1953

`cities`前面的`ONLY`表明该查询应该只针对`cities` 而不包括其后代。许多我们已经讨论过的命令(`SELECT`, `UPDATE` 和 `DELETE`)都支持`ONLY`关键字。

你也可以在表名后面写一个`*`显示指定包括所有后代表：

    SELECT name, altitude
        FROM cities*
        WHERE altitude > 500;

因为这个行为是默认的，所以写`*`并不是必须的（除非你已经改变了 sql_inheritance里面的配置选项）。然而，写`*` 可以用于强调搜索额外的表。

有时候你可能想知道某个行版本来自哪个表。在每个表里我们都有一个`tableoid` 系统属性可以告诉你源表是谁：

    SELECT c.tableoid, c.name, c.altitude
    FROM cities c
    WHERE c.altitude > 500;

结果如下(你可能会得到不同的 OID)：

     tableoid |   name    | altitude
    ----------+-----------+----------
       139793 | Las Vegas |     2174
       139793 | Mariposa  |     1953
       139798 | Madison   |      845

通过和`pg_class`做一个连接，就可以看到实际的表名字：

    SELECT p.relname, c.name, c.altitude
    FROM cities c, pg_class p
    WHERE c.altitude > 500 AND c.tableoid = p.oid;

它返回：

     relname  |   name    | altitude
    ----------+-----------+----------
     cities   | Las Vegas |     2174
     cities   | Mariposa  |     1953
     capitals | Madison   |      845

对于`INSERT`或`COPY`，继承并不自动影响其后代表。 在我们的例子里，下面的`INSERT`语句将会失败：

    INSERT INTO cities (name, population, altitude, state)
    VALUES ('New York', NULL, NULL, 'NY');

我们可能希望数据被传递到`capitals`表里面去， 但这是不会发生的：`INSERT`总是插入明确声明的那个表。 在某些情况下，我们可以使用规则进行重定向插入(参阅 第 38 章)。 不过它不能对上面的例子有什么帮助，因为`cities`表并不包含`state` 字段，因此命令在规则施加之前就会被拒绝掉。

所有父表的检查约束和非空约束都会自动被所有子表继承。 不过其它类型的约束(唯一、主键、外键约束)不会被继承。

一个子表可以从多个父表继承，这种情况下它将拥有所有父表字段的总和， 并且子表中定义的字段也会加入其中。如果同一个字段名出现在多个父表中， 或者同时出现在父表和子表的定义里，那么这些字段就会被"融合"， 这样在子表里就只有一个这样的字段。要想融合，字段的数据类型必须相同， 否则就会抛出一个错误。融合的字段将会拥有其父字段的所有检查约束， 并且如果某个父字段存在非空约束，那么融合后的字段也必须是非空的。

表继承通常使用带`INHERITS`子句的CREATE TABLE语句定义。 另外，一个已经用此方法定义的子表可以使用带`INHERIT`的ALTER TABLE 命令添加一个新父表。注意：该子表必须已经包含新父表的所有字段且类型一致， 此外新父表的每个约束的名字及其表达式都必须包含在此子表中。同样， 一个继承链可以使用带`NO INHERIT`的`ALTER TABLE`命令从子表上删除。 允许动态添加和删除继承链对基于继承关系的表分区(参见第 5.9 节)很有用。

创建一个将要作为子表的新表的便利途径是使用带`LIKE`子句的 `CREATE TABLE`命令。它将创建一个与源表字段相同的新表。 如果源表中存在约束，那么应该指定`LIKE`的`INCLUDING CONSTRAINTS` 选项，因为子表必须包含源表中的`CHECK`约束。

任何存在子表的父表都不能被删除，同样，子表中任何从父表继承的字段或约束也不能被删除或修改。 如果你想删除一个表及其所有后代，最简单的办法是使用`CASCADE`选项删除父表。

ALTER TABLE会把所有数据定义和检查约束传播到后代里面去。另外， 只有在使用`CASCADE`选项的情况下，才能删除依赖于其他表的字段。 `ALTER TABLE`在重复字段融合和拒绝方面和`CREATE TABLE`的规则相同。

请注意表访问权限是如何处理的。访问父表会自动访问在子表中的数据，而不需要更多的访问权限检查。 这保留了父表中数据的表现。然而，直接访问子表不会自动允许访问父表，要访问父表需要更进一步的权限被授予。

## 5.8.1. 警告

注意，不是所有的 SQL 命令可以在所有的继承层次上正常工作。数据查询，数据修改， 模式修改的命令（比如`SELECT`，`UPDATE`，`DELETE`， `ALTER TABLE`的大多数变型，但不是`INSERT`和 `ALTER TABLE ... RENAME`）典型的默认包括子表和支持`ONLY`符号来排除它们。 为数据库维护和调优的命令（例如`REINDEX`，`VACUUM`） 通常只对个别工作，物理表格不支持递归超过继承层次结构。单独命令各自的行为记录在了它们的参考页中(参考 I, SQL 命令)。

继承的一个严重局限性是索引(包括唯一约束)和外键约束只能用于单个表， 而不能包括它们的子表(不管对外键约束的引用表还是被引用表都是如此)，因此，在上面的例子里：

- 即使我们声明`cities`.`name`为`UNIQUE`或`PRIMARY KEY`， 也不会阻止`capitals`表拥有重复名字的`cities`数据行。 并且这些重复的行在查询`cities`表的时候会显示出来。实际上， 缺省时`capitals`将完全没有唯一约束，因此可能包含带有同名的多个行。 你应该给`capitals`增加唯一约束，但即使这样做也不能避免与`cities`的重复。

- 类似的，即使我们声明`cities`.`name` 参照（`REFERENCES`） 某些其它的表，这个约束也不会自动传播到`capitals`表。在这种条件下， 你可以通过手工给`capitals`表增加同样的`REFERENCES`约束来做到这点。

- 声明一个其它表的字段为`REFERENCES cities(name)`将允许其它表包含城市名， 但是不包含首府名。这种情况下没有很好的绕开办法。

这些缺点很可能在将来的版本中修补，但同时你也需要考虑一下，继承是否对你的应用真正有用。

上一页 | 起始页 | 下一页
模式 | 上一级 | 分区