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
權限意味著也有權限更新 capitals
表格中的列,當它們通過 cities
存取時。 這保留了資料(也)在父表格中的外觀。 但是,如果沒有額外的授權,就不能直接更新 capitals
表格。 以類似的方式,父表格的列安全性原則(請參閱第 5.9 節)在繼承查詢期間應用於來自子表格的列。 子表格的原則(如果有的話)僅在它是查詢中明確命名的表格時才適用; 在這種情況下,附加到其父表格的任何原則都將被忽略。
外部表格(請參閱第 5.13 節)也可以是繼承階層的一部分,無論是作為父表格還是子表格,就像常規表格一樣。 如果外部表格是繼承階層的一部分,則外部表格不支援的任何操作也不支援整個階層。
請注意,並非所有 SQL 命令都能在繼承階層上工作。 用於資料查詢、資料修改或結構描述修改的命令(例如,SELECT
、UPDATE
、DELETE
、大多數 ALTER TABLE
變體,但不包括 INSERT
或 ALTER TABLE ... RENAME
)通常預設為包含子表格,並支援 ONLY
表示法以排除它們。 用於執行資料庫維護和調整的命令(例如,REINDEX
、VACUUM
)通常僅適用於個別的實體表格,而不支援在繼承階層上遞迴。 每個個別命令的相應行為記錄在其參考頁面中(SQL 命令)。
繼承功能的一個嚴重的限制是索引(包括唯一約束)和外部鍵約束僅適用於單個表格,而不適用於其繼承子表格。 這在外部鍵約束的參考和被參考端都是如此。 因此,根據上面的範例
如果我們宣告 cities
.name
為 UNIQUE
或 PRIMARY KEY
,這不會阻止 capitals
表格擁有名稱與 cities
中的列重複的列。 預設情況下,這些重複的列將顯示在來自 cities
的查詢中。 事實上,預設情況下,capitals
根本沒有唯一約束,因此可能包含具有相同名稱的多個列。 您可以將唯一約束新增到 capitals
,但這不能防止與 cities
相比的重複。
同樣,如果我們要指定 cities
.name
REFERENCES
某些其他表格,則此約束不會自動傳播到 capitals
。 在這種情況下,您可以通過手動將相同的 REFERENCES
約束新增到 capitals
來解決此問題。
指定另一個表格的列 REFERENCES cities(name)
將允許另一個表格包含城市名稱,但不包含首都名稱。 對於這種情況,沒有好的解決方法。
某些未為繼承階層實作的功能已為宣告式分割實作。 在決定是否使用舊版繼承進行分割對您的應用程式有用的時候,需要格外小心。
如果您在文件中發現任何不正確、與您特定功能的經驗不符或需要進一步澄清的地方,請使用此表格來回報文件問題。