PostgreSQLは、データベース設計者にとって便利なテーブルの継承を実装しています。 (SQL:1999以降は型の継承を定義していますが、ここで述べられている継承とは多くの点で異なっています。)
まず例から始めましょう。
市(cities)のデータモデルを作成しようとしていると仮定してください。
それぞれの州にはたくさんの市がありますが、州都(capitals)は1つのみです。
どの州についても州都を素早く検索したいとします。
これは、2つのテーブルを作成することにより実現できます。
1つは州都のテーブルで、もう1つは州都ではない市のテーブルです。
しかし、州都であるか否かに関わらず、市に対するデータを問い合わせたいときには何が起こるのでしょうか?
継承はこの問題を解決できます。
cities
から継承されるcapitals
テーブルを定義するのです。
CREATE TABLE cities ( name text, population float, elevation int -- in feet ); CREATE TABLE capitals ( state char(2) ) INHERITS (cities);
この場合、capitals
テーブルは、その親テーブルであるcities
テーブルの列をすべて継承します。
州都は1つの追加の列state
を持ち、州を表現します。
PostgreSQLでは、1つのテーブルは、0以上のテーブルから継承することが可能です。 また、問い合わせはテーブルのすべての行、またはテーブルのすべての行と継承されたテーブルのすべての行のいずれかを参照できます。 後者がデフォルトの動作になります。 例えば次の問い合わせは、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
は、いつも指定されたテーブルそれ自体に対してデータを挿入します。
ルール(詳細は第41章を参照してください)を使用して挿入を中継できる場合もあります。
しかし、ルールを使用しても上記のような場合は解決できません。
なぜなら、cities
テーブルにstate
列が含まれていないため、ルールが適用される前にコマンドを拒否されてしまうからです。
親テーブル上の検査制約と非NULL制約は、NO INHERIT
句によって明示的に指定されない限り、その子テーブルに自動的に継承されます。
他の種類の制約(一意性制約、主キー、外部キー制約)は継承されません。
テーブルは1つ以上の親テーブルから継承可能です。 この場合、テーブルは親テーブルで定義された列の和になります。 子テーブルで宣言された列は、これらの列に追加されることになります。 もし親テーブルに同じ名前の列がある場合、もしくは、親テーブルと子テーブルに同じ名前の列がある場合は、列が「統合」されて子テーブルではただ1つの列となります。 統合されるには列は同じデータ型を持っている必要があります。 異なるデータ型の場合にはエラーとなります。 継承可能な検査制約と非NULL制約は、同じようなやり方で統合されます。 つまり、例えば、列定義のいずれかが非NULL制約の印が付いているならば、統合された列に非NULLという印が付きます。 検査制約は、同じ名前を持っている場合に統合され、それらの条件が異なる場合は統合に失敗します。
テーブル継承は、通常、CREATE TABLE
文のINHERITS
句を使用して、子テーブルを作成する時に確立します。
他にも、互換性を持つ方法で定義済みのテーブルに新しく親子関係を付けることも可能です。
これにはALTER TABLE
のINHERIT
形式を使用します。
このためには、新しい子テーブルは親テーブルと同じ名前の列を持ち、その列の型は同じデータ型でなければなりません。
また、親テーブルと同じ名前、同じ式の検査制約を持っていなければなりません。
ALTER TABLE
のNO INHERIT
形式を使用して、同様に継承関係を子テーブルから取り除くことも可能です。
このような継承関係の動的追加、動的削除は、継承関係をテーブルパーティショニング(5.11を参照)に使用している場合に有用です。
後で子テーブルとする予定の、互換性を持つテーブルを簡単に作成する方法の1つは、CREATE TABLE
でLIKE
句を使用することです。
これは、元としたテーブルと同じ列を持つテーブルを新しく作成します。
新しい子テーブルが必ず親テーブルと一致する制約を持ち、互換性があるものとみなされるように、元となるテーブルでCHECK
制約が存在する場合は、LIKE
にINCLUDING CONSTRAINTS
オプションを指定すべきです。
子テーブルが存在する場合親テーブルを削除することはできません。
また、子テーブルでは、親テーブルから継承した列、または検査制約を削除することも変更することもできません。
テーブルとそのすべての子テーブルを削除したければ、CASCADE
オプションを付けて親テーブルを削除することが簡単な方法です(5.14を参照)。
ALTER TABLE
は、列データ定義と検査制約の変更を継承の階層にあるテーブルに伝えます。
ここでも、他のテーブルに依存する列の削除はCASCADE
オプションを使用したときのみ可能となります。
ALTER TABLE
は、重複列の統合と拒否について、CREATE TABLE
時に適用される規則に従います。
継承された問い合わせは、親テーブルのみアクセス権限を検査します。
つまり、例えば、UPDATE
権限をcities
テーブルに付与することは、cities
テーブルを通じてアクセスする場合に、capitals
テーブルにも行の更新権限を付与することを意味します。
これによりデータが親テーブルに(も)あるように見えることが保たれます。
しかし、capitals
テーブルは、追加権限なしに直接更新することはできません。
同様に、親テーブルの行セキュリティポリシー(5.8を参照してください)が、継承された問い合わせの時に子テーブルの行に適用されます。
子テーブルのポリシー(あれば)は、問い合わせにて明示的に指定されたテーブルである時にのみ適用されます。
そしてこの場合、親テーブルに紐付けられたあらゆるポリシーは無視されます。
外部テーブル(5.12参照)も通常のテーブルと同様、親テーブルあるいは子テーブルとして継承の階層の一部となりえます。 外部テーブルが継承の階層の一部となっている場合、外部テーブルがサポートしない操作は、その継承全体でもサポートされません。
すべてのSQLコマンドが継承階層に対して動作できるとは限らないことに注意してください。
データの検索、データの変更、スキーマの変更のために使用されるコマンド(例えばSELECT
、UPDATE
、DELETE
、ALTER TABLE
のほとんどの構文が該当しますが、INSERT
やALTER TABLE ... RENAME
は含まれません)は通常、デフォルトで子テーブルを含み、また、それを除外するためのONLY
記法をサポートします。
データベース保守およびチューニング(例えばREINDEX
、VACUUM
)を行うコマンドは通常、個々の物理テーブルに対してのみ動作し、継承階層に対する再帰をサポートしません。
個々のコマンドのそれぞれの動作はそのマニュアルページ(SQLコマンド)に記載されています。
継承機能の重大な制限として、インデックス(一意性制約を含む)、および外部キーは、そのテーブルのみに適用され、それを継承した子テーブルには適用されないことがあります。 これは外部キーの参照側、被参照側の両方について当てはまります。 したがって、上の例では
もし、cities
.name
をUNIQUE
またはPRIMARY KEY
と宣言しても、cities
テーブルの行と重複した行をcapitals
テーブル内に持つことを禁止することにはなりません。
さらに、これらの重複した行はデフォルトでcities
テーブルへの問い合わせで現れるでしょう。
事実として、capitals
テーブルはデフォルトで一意性制約を持っていませんし、同一の名前の複数の行を持つことがあり得ます。
capitals
テーブルに一意性制約を追加できますが、これはcities
テーブルと比較して重複を禁止することにはなりません。
同じように、cities
.name
REFERENCES
で他のテーブルを参照するようにしても、この制約は自動的にcapitals
に引き継がれるわけではありません。
この場合はcapitals
テーブルに同一のREFERENCES
制約を手動で追加すれば問題を回避できます。
他のテーブルの列にREFERENCES cities(name)
を指定すると、他のテーブルが市の名前を持つことはできますが、州都の名前を持つことはできません。
この場合は良い回避策がありません。
継承の階層に対して実装されていないいくつかの機能は、宣言的パーティショニングでは実装されています。 従来の継承によるパーティショニングがアプリケーションにとって有用であるかどうかを判断する際に十分注意してください。