全部產品
Search
文件中心

PolarDB:繼承

更新時間:Jul 06, 2024

本文介紹了繼承的相關內容。

簡介

本資料庫實現了表繼承,這對資料庫設計者來說是一種有用的工具。

讓我們從一個例子開始:假設我們要為城市建立一個資料模型。每一個州有很多城市,但是只有一個首府。我們希望能夠快速地檢索任何特定州的首府城市。這可以通過建立兩個表來實現:一個用於州首府,另一個用於不是首府的城市。然而,當我們想要查看一個城市的資料(不管它是不是一個首府)時會發生什嗎?繼承特性將有助於解決這個問題。我們可以將capitals表定義為繼承自cities

    CREATE TABLE cities (
        name            text,
        population      float,
        elevation       int     -- in feet
    );

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

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

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

    SELECT name, elevation
        FROM cities
        WHERE elevation > 500;

對於來自本資料庫教程的例子資料,它將返回:

       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之下的其他表都不會被該查詢涉及。很多我們已經討論過的命令(如SELECTUPDATEDELETE)都支援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;

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

    INSERT INTO cities (name, population, elevation, state)
    VALUES ('Albany', NULL, NULL, 'NY');

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

父表上的所有檢查約束和非空約束都將自動被它的後代所繼承,除非顯式地指定了NO INHERIT子句。其他類型的約束(唯一、主鍵和外鍵約束)則不會被繼承。

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

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

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

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

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

繼承的查詢僅在附表上執行存取權限檢查。例如,在cities表上授予UPDATE許可權也隱含著通過cities訪問時在capitals表中更新行的許可權。 這保留了資料(也)在父表中的樣子。但是如果沒有額外的授權,則不能直接更新capitals表。 以類似的方式,父表的行安全性策略適用於繼承查詢期間來自於子表的行。 只有當子表在查詢中被明確提到時,其策略(如果有)才會被應用,在那種情況下,附著在其父表上的任何策略都會被忽略。

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

說明

並非所有的 SQL 命令都能工作在繼承層次上。用於資料查詢、資料修改或模式修改(例如SELECTUPDATEDELETE、大部分ALTER TABLE的變體,但INSERTALTER TABLE ... RENAME不在此列)的命令會預設將子表包含在內並且支援ONLY記號來排除子表。負責資料庫維護和調整的命令(如REINDEXVACUUM)只工作在獨立的、物理的表上並且不支援在繼承層次上的遞迴。

繼承特性的一個嚴肅的限制是索引(包括唯一約束)和外鍵約束值應用在單個表上而非它們的繼承子女。在外鍵約束的引用端和被引用端都是這樣。因此,按照上面的例子:

  • 如果我們聲明cities.nameUNIQUE或者PRIMARY KEY,這將不會阻止capitals表中擁有和cities中城市同名的行。而且這些重複的行將會預設顯示在cities的查詢中。事實上,capitals在預設情況下是根本不能擁有唯一約束的,並且因此能夠包含多個同名的行。我們可以為capitals增加一個唯一約束,但這無法阻止相對於cities的重複。

  • 相似地,如果我們指定cities.name REFERENCES某個其他表,該約束不會自動地傳播到capitals。在此種情況下,我們可以變通地在capitals上手工建立一個相同的REFERENCES約束。

  • 指定另一個表的列REFERENCES cities(name)將允許其他表包含城市名稱,但不會包含首府名稱。這對於這個例子不是一個好的變通方案。

某些未為繼承階層實現的功能是為聲明性分區實現的。在決定使用舊繼承進行分區是否對應用程式有用時,需要非常小心。