5.8. 继承

PostgreSQL 实现了表继承,这个特性可能对数据库设计人员来说是一个大好消息。 (SQL:1999 以及以后的标准定义了类型继承特性,和我们在这里描述的很多特性有区别。)

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

CREATE TABLE cities (
    name            text,
    population      float,
    altitude        int     -- (单位:英尺)
);

CREATE TABLE capitals (
    state           char(2)
) INHERITS (cities);

在这种情况下,一行首府从它的父表,cities继承所有属性。 州首府有一个额外的属性, state,显示它们所在的州。

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

SELECT name, altitude
    FROM cities
    WHERE altitude > 500;

使用 PostgreSQL 教程里面的数据(参阅 Section 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 进行查找而不包括继承级别低于 cities 的表。 许多我们已经讨论过的命令 -- SELECTUPDATEDELETE -- 支持这个 "ONLY" 符号。

继承与权限: 因为权限并非是自动继承的,所以,企图访问一个父表的用户必须要么有和子表相同的权限, 要么必须使用 "ONLY" 表示法。或者在现有的系统里创建一个新的继承关系, 不过要小心这样做不会带来更多问题。

另外,下面的查询找出所有不是州府并且海拔高于 500 英尺的城市:

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

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

这里的 ONLY 关键子表示这个查询只检索 cities,而不包括继承级别里 cities 下面的表。 许多我们已经讨论过的命令 — SELECTUPDATEDELETE — 支持 ONLY 关键子。

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

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

它返回:

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

(如果你想复现这个例子,你可能会得到不同的数字 OID。) 通过和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 总是插入明确声明的那个表。 在某种场合下,我们可以使用规则重定向插入 (参阅 Chapter 34)。不过它不能对上面的例子有什么帮助, 因为 cities 表并不包含字段 state, 因此命令在规则施加之前就会被拒绝掉。

在表上可以定义广播到继承层次里的检查约束。所有父表的检查约束都会自动被所有子表继承。 不过其它类型的约束没有被继承。

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

表继承目前只能用 CREATE TABLE 语句定义。 相关的语句 CREATE TABLE AS 不允许声明继承。 目前没有办法给现有的表加一个连接使之成为一个子表。 相似的还有,继承定义了之后,没有办法从一个子表上删除一个继承关系, 除非是删除整个表。如果子表存在,则不能删除父表。 如果你想删除一个表和其所有后代,一个简单的方法是用 CASCADE 选项删除全部表。

ALTER TABLE 会把所有数据定义和检查约束广播到继承层次里面去。 另外,只有在使用 CASCADE 选项的情况下,才能删除父表的字段或者约束。 ALTER TABLE 在重复字段融合和拒绝方面和 CREATE TABLE 的情况一样。

5.8.1. 注意事项

表访问权限并不会自动继承。因此,一个试图访问父表的用户还必须具有访问它的所有子表的权限, 或者必须使用 ONLY 表示法。在向现有的继承层次添加新的子表的时候, 请注意给它赋予所有权限。

继承特性的一个严重的局限性是索引(包括唯一约束)和外键约束只施用于单个表, 而不包括它们的继承的子表。这一点不管对引用表还是被引用表都是事实,因此,在上面的例子里:

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

废弃: 在以前的 PostgreSQL 版本里, 缺省的行为是不在查询里包含子表。后来发现这么做很容易出错并且也违反了 SQL 标柱。 在老的语法里,要包含子表,你需要在表名字后面附加一个 *。比如:

SELECT * from cities*;

你仍然可以用附加 * 的方法明确声明扫描子表, 以及写 ONLY 的方法明确声明不扫描子表。 不过,从版本 7.1 开始,对无修饰的表名称的缺省行为已经修改成扫描其子表, 而以前的缺省是不这么做。要想获得老的缺省行为, 可以关闭配置参数 sql_inheritance