PostgreSQL 表繼承

eric0435發表於2019-10-30

PostgreSQL實現了表繼承,這對資料庫設計者來說是一種有用的工具(SQL:1999及其後的版本定義了一種型別繼承特性,但和這裡介紹的繼承有很大的不同)。讓我們從一個例子開始:假設我們要為城市建立一個資料模型。每一個州有很多城市,但是隻有一個首府。我們希望能夠快速地檢索任何特定州的首府城市。這可以透過建立兩個表來實現:一個用於州首府,另一個用於不是首府的城市。然而,當我們想要檢視一個城市的資料(不管它是不是一個首府)時會發生什麼?繼承特性將有助於解決這個問題。我們可以將capitals表定義為繼承自cities表:

jydb=# CREATE TABLE cities (
jydb(# name text,
jydb(# population float,
jydb(# altitude int -- in feet
jydb(# );
CREATE TABLE
jydb=# CREATE TABLE capitals (
jydb(# state char(2)
jydb(# ) INHERITS (cities);
CREATE TABLE
jydb=# insert into cities values('Las Vegas',600,2174);
INSERT 0 1
jydb=# insert into cities values('Mariposa',500,1953);
INSERT 0 1
jydb=# insert into cities values('Madison',450,845);
INSERT 0 1
jydb=# insert into capitals values('Houston',400,745,'LA');
INSERT 0 1
jydb=# select * from cities;
   name    | population | altitude
-----------+------------+----------
 Las Vegas |        600 |     2174
 Mariposa  |        500 |     1953
 Madison   |        450 |      845
 Houston   |        400 |      745
(4 rows)
jydb=# select * from capitals;
  name   | population | altitude | state
---------+------------+----------+-------
 Houston |        400 |      745 | LA
(1 row)

在這種情況下,capitals表繼承了它父表cities的所有列。州首府還有一個額外的列state用來表示它所屬的州。

在PostgreSQL中,一個表可以從0個或者多個其他表繼承,而對一個表的查詢則可以引用一個表的所有行或者該表的所有行加上它所有的後代表。預設情況是後一種行為。例如,下面的查詢將查詢所有海拔高於500尺的城市的名稱,包括州首府:

jydb=# SELECT name, altitude FROM cities WHERE altitude > 500;
   name    | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953
 Madison   |      845
 Houston   |      745
(4 rows)

在另一方面,下面的查詢將找到海拔超過500尺且不是州首府的所有城市:

jydb=# SELECT name, altitude FROM ONLY cities WHERE altitude > 500;
   name    | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953
 Madison   |      845
(3 rows)

這裡的ONLY關鍵詞指示查詢只被應用於cities上,而其他在繼承層次中位於cities之下的其他表都不會被該查詢涉及。很多我們已經討論過的命令(如SELECT、UPDATE和DELETE)都支援ONLY關鍵詞。

我們也可以在表名後寫上一個*來顯式地將後代表包括在查詢範圍內:

jydb=# SELECT name, altitude FROM cities* WHERE altitude > 500;
   name    | altitude
-----------+----------
 Las Vegas |     2174
 Mariposa  |     1953
 Madison   |      845
 Houston   |      745
(4 rows)

*並不是必須的,因為它對應的行為是預設的(除非改變sql_inheritance配置選項的設定)。但是書寫*有助於強調會有附加表被搜尋。

在某些情況下,我們可能希望知道一個特定行來自於哪個表。每個表中的系統列tableoid可以告訴我們行來自於哪個表:

jydb=# SELECT c.tableoid, c.name, c.altitude FROM cities c WHERE c.altitude > 500;
 tableoid |   name    | altitude
----------+-----------+----------
    24653 | Las Vegas |     2174
    24653 | Mariposa  |     1953
    24653 | Madison   |      845
    24659 | Houston   |      745
(4 rows)

(如果重新生成這個結果,可能會得到不同的OID數字。)透過與pg_class進行連線可以看到實際的表名:

jydb=# SELECT p.relname, c.name, c.altitude
jydb-# FROM cities c, pg_class p
jydb-# WHERE c.altitude > 500 AND c.tableoid = p.oid;
 relname  |   name    | altitude
----------+-----------+----------
 cities   | Las Vegas |     2174
 cities   | Mariposa  |     1953
 cities   | Madison   |      845
 capitals | Houston   |      745
(4 rows)

另一種得到同樣效果的方法是使用regclass偽型別, 它將象徵性地列印出表的 OID:

jydb=# SELECT c.tableoid::regclass, c.name, c.altitude
jydb-# FROM cities c
jydb-# WHERE c.altitude > 500;
 tableoid |   name    | altitude
----------+-----------+----------
 cities   | Las Vegas |     2174
 cities   | Mariposa  |     1953
 cities   | Madison   |      845
 capitals | Houston   |      745
(4 rows)

繼承不會自動地將來自INSERT或COPY命令的資料傳播到繼承層次中的其他表中。在我們的例子中,下面的INSERT語句將會失敗:

jydb=# INSERT INTO cities (name, population, altitude, state) VALUES (’Albany’, NULL, NULL, ’NY’);
ERROR:  column "state" of relation "cities" does not exist
LINE 1: INSERT INTO cities (name, population, altitude, state) VALUE...

^

我們也許希望資料能被以某種方式被引入到capitals表中,但是這不會發生:INSERT總是向指定的表中插入。在某些情況下,可以透過使用一個規則(見第 39 章)來將插入動作重定向。但是這對上面的情況並沒有幫助,因為cities表根本就不包含state列,因而這個命令將在觸發規則之前就被拒絕。

父表上的所有檢查約束和非空約束都將自動被它的後代所繼承。其他型別的約束(唯一、主鍵和外來鍵約束)則不會被繼承。

一個表可以從多個父表繼承,在這種情況下它擁有父表們所定義的列的並集。任何定義在子表上的列也會被加入到其中。如果在這個集合中出現重名列,那麼這些列將被"合併",這樣在子表中只會有一個這樣的列。重名列能被合併的前提是這些列必須具有相同的資料型別,否則會導致錯誤。可繼承的檢查約束和非空約束以類似的方式合併。因此,例如,如果任何列定義被標記為not-null,則合併列將被標記為not-null。如果檢查約束具有相同的名稱,則合併它們;如果條件不同,則合併將失敗。

表繼承通常是在子表被建立時建立,使用CREATE TABLE語句的INHERITS子句。一個已經被建立的表也可以另外一種方式增加一個新的父親關係,使用ALTER TABLE的INHERIT變體。要這樣做,新的子表必須已經包括和父表相同名稱和資料型別的列。子表還必須包括和父表相同的檢查約束和檢查表示式。相似地,一個繼承連結也可以使用ALTER TABLE的 NO INHERIT變體從一個子表中移除。動態增加和移除繼承連結可以用於實現表劃分

一種建立一個未來將被用做子女的新表的方法是在CREATE TABLE中使用LIKE子句。這將建立一個和源表具有相同列的新表。如果源表上定義有任何CHECK約束,LIKE的INCLUDING CONSTRAINTS選項可以用來讓新的子表也包含和父表相同的約束。

當有任何一個子表存在時,父表不能被刪除。當子表的列或者檢查約束繼承於父表時,它們也不能被刪除或修改。如果希望移除一個表和它的所有後代,一種簡單的方法是使用CASCADE選項刪除父表

ALTER TABLE將會把列的資料定義或檢查約束上的任何變化沿著繼承層次向下傳播。同樣,刪除被其他表依賴的列只能使用CASCADE選項。ALTER TABLE對於重名列的合併和拒絕遵循與CREATE TABLE同樣的規則。

繼承查詢只對父表執行訪問許可權檢查。因此,例如,對cities表授予update許可權會意味著透過cities訪問capitals表時也能更新capitals表。這體現了子表中的資料也在父表中。但是capitals表在沒有額外地授權情況下不能被直接更新。以類似的方式,父表的行安全策略在執行繼承查詢時會應用到子表的行記錄。子表的策略(如果有的話)僅當它是查詢中顯式命名的表時才應用;在這種情況下,任何附加到其父級的策略都將被忽略。

外部表也可以是繼承層次 中的一部分,即可以作為父表也可以作為子表,就像常規表一樣。如果一個外部表是繼承層次的一部分,那麼任何不被該外部表支援的操作也不被整個層次所支援。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26015009/viewspace-2661919/,如需轉載,請註明出處,否則將追究法律責任。

相關文章