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
許可權,意味著當透過 cities
訪問 capitals
表中的行時,也具有更新這些行的許可權。這保留了資料(也)位於父表中的外觀。但是,如果沒有額外的授權,則無法直接更新 capitals
表。以類似的方式,在繼承查詢中,父表的行安全策略(請參見 第 5.9 節)適用於來自子表的行。子表的策略(如果存在)僅在它作為查詢中明確命名的表時應用;在這種情況下,其父表上的任何策略都會被忽略。
外部表(請參見 第 5.13 節)也可以是繼承層次結構的一部分,無論是作為父表還是子表,就像常規表一樣。如果外部表是繼承層次結構的一部分,那麼任何外部表不支援的操作在整個層次結構上也不支援。
請注意,並非所有 SQL 命令都能在繼承層次結構上工作。用於資料查詢、資料修改或模式修改的命令(例如,SELECT
、UPDATE
、DELETE
、大多數 ALTER TABLE
的變體,但不包括 INSERT
或 ALTER TABLE ... RENAME
)通常預設包含子表,並支援 ONLY
表示法來排除它們。大多數執行資料庫維護和調優的命令(例如,REINDEX
)僅在單個物理表上工作,並且不支援遍歷繼承層次結構。VACUUM
和 ANALYZE
命令預設包含子表,並且支援 ONLY
表示法以允許它們被排除。每個命令的具體行為在其參考頁(SQL 命令)中有記錄。
繼承特性的一個嚴重限制是,索引(包括唯一約束)和外部索引鍵約束僅適用於單個表,而不適用於它們的繼承子表。在引用方和被引用方外部索引鍵約束上都是如此。因此,根據上面的例子
如果我們宣告 cities
.name
為 UNIQUE
或 PRIMARY KEY
,這不會阻止 capitals
表中出現與 cities
表中行重複的名稱。而且,預設情況下,這些重複的行會出現在從 cities
發起的查詢中。實際上,預設情況下 capitals
將沒有任何唯一約束,因此可能包含多個名稱相同的行。您可以為 capitals
新增一個唯一約束,但這不會阻止與 cities
相比的重複。
同樣,如果我們指定 cities
.name
REFERENCES
另一個表,此約束不會自動傳播到 capitals
。在這種情況下,您可以透過手動為 capitals
新增相同的 REFERENCES
約束來解決。
指定另一個表的列 REFERENCES cities(name)
將允許另一個表包含城市名稱,但不能包含首府名稱。在這種情況下沒有好的解決方法。
為繼承層次結構未實現的一些功能已在宣告式分割槽中實現。在決定是否為應用程式使用遺留繼承分割槽時需要非常謹慎。
如果您在文件中看到任何不正確、與您對特定功能的體驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。