他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

5.4. 制約 #

データ型は、テーブルに格納するデータの種類を限定するための方法です。 しかし、多くのアプリケーションでは、型が提供する制約では精密さに欠けます。 例えば、製品の価格が入る列には、おそらく正数のみを受け入れるようにする必要があります。 しかし、正数のみを受け入れるという標準のデータ型はありません。 また、他の列や行に関連して列データを制約したい場合もあります。 例えば、製品の情報が入っているテーブルでは、1つの製品番号についての行が2行以上あってはなりません。

このような問題を解決するため、SQLでは列およびテーブルに対する制約を定義できます。 制約によってテーブル内のデータを自由に制御できます。 制約に違反するデータを列に格納しようとすると、エラーとなります。 このことは、デフォルト値として定義された値を格納する場合にも適用されます。

5.4.1. 検査制約 #

検査制約は最も汎用的な制約の種類です。 これを使用して、特定の列の値が論理値の式を満たす(真の値)ように指定できます。 例えば、製品価格を必ず正数にするには以下のようにします。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0)
);

このように、制約の定義はデフォルト値の定義と同様に、データ型の後にきます。 デフォルト値と制約は任意の順序で列挙できます。 検査制約はCHECKキーワードの後に続く括弧で囲まれた式で構成されます。 検査制約式には、制約される列を含む必要があります。 そうしないと、制約はあまり意味のないものになります。

制約に個別に名前を付けることもできます。 名前を付けることで、エラーメッセージがわかりやすくなりますし、変更したい制約を参照できます。 構文は以下のとおりです。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CONSTRAINT positive_price CHECK (price > 0)
);

上記のように、名前付き制約の指定はCONSTRAINTキーワードで始め、これに識別子、制約定義と続きます。 (この方法で制約名を指定しない場合は、システムにより名前が付けられます。)

検査制約では複数の列を参照することもできます。 例えば、通常価格と割引価格を格納する場合に、必ず割引価格が通常価格よりも低くなるようにしたいとします。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0),
    discounted_price numeric CHECK (discounted_price > 0),
    CHECK (price > discounted_price)
);

最初の2つの制約は上で説明した通りです。 3つ目の制約では新しい構文を使っています。 これは特定の列に付加されるのではなく、カンマで区切られた列リスト内の別個の項目として現れます。 列定義およびこれらの制約定義は、任意の順序で列挙できます。

最初の2つの制約を列制約と言います。これに対し、3つ目の制約は列定義とは別個に書かれるので、テーブル制約と言います。 列制約をテーブル制約として書くことはできますが、その逆はできる場合とできない場合があります。なぜなら列制約は、制約に関連付けられている列のみを参照するためです。 (PostgreSQLはこの規則を強制しません。しかし、作成したテーブル定義を他のデータベースシステムでも動作させたい場合はこの規則に従ってください。) 上の例は、以下のように書くこともできます。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CHECK (price > discounted_price)
);

あるいは、次のようにもできます。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0 AND price > discounted_price)
);

どのようにするかは好みの問題です。

列制約と同様に、テーブル制約に名前を割り当てることができます。

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CONSTRAINT valid_discount CHECK (price > discounted_price)
);

検査制約では、検査式が真またはNULL値と評価された場合に、条件が満たされることに注意してください。 ほとんどの式は、演算項目に一つでもNULLがあればNULLと評価されるので、検査制約では制約対象の列にNULL値が入るのを防げません。 列がNULL値を含まないようにするために、次節で説明する非NULL制約を使用できます。

注記

PostgreSQLは、検査対象の新しい行もしくは更新対象行以外のテーブルデータを参照するCHECK制約はサポートしていません。 このルールに違反するCHECK制約は単純なテストでは動いたように見えますが、(関連する他の行が後で更新されたことにより)データベースがその制約条件が偽になるような状態にならないことを保証できません。 これによってデータベースのダンプとリストアの失敗が引き起こされるでしょう。 最終的なデータベース状態が制約に対して一貫した状態であったとしても、制約を満たす順で行がロードされないことによりリストアは失敗することがあります。 可能ならばUNIQUEEXCLUDEFOREIGN KEY制約を使って行あるいはテーブルをまたがる制約を表現してください。

常に一貫性の保障を維持するのではなく、行挿入の際に一回だけの行の検査が必要なら、その実装のためにカスタムトリガが利用できます。 (pg_dumpはデータのリストア後までトリガを再インストールせず、ダンプ/リストア中は検査が強制されないため、この方法でダンプ/リストア問題を回避できます。)

注記

PostgreSQLCHECK制約の条件が不変であると仮定します。 つまり同じ入力行に対して常に同じ結果が返るということです。 この仮定によりCHECK制約が挿入あるいは更新時にのみ検査され、他のときには検査されないことが正当化されます。 (他のテーブルデータを参照しないことによる上述の警告はこの制限の本当に特別な場合です。)

この仮定に反する一般的な例は、CHECK式でユーザ定義関数を参照し、その関数の振る舞いを変更することです。 PostgreSQLはこれを禁止しませんが、今やCHECK制約に違反する行がテーブル中に存在することを通知しません。 これによって後でデータベースのダンプとリストアの失敗を引き起こすでしょう。 そのような変更に対処するおすすめの方法は、(ALTER TABLEを使って)制約を削除し、関数定義を調整し、そして制約を再度追加して、それによってテーブル全体の行に対して再チェックを行うことです。

5.4.2. 非NULL制約 #

非NULL制約は単純に、列がNULL値を取らないことを指定します。 構文の例は以下のとおりです。

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric
);

非NULL制約は常に列制約として記述されます。 非NULL制約はCHECK (column_name IS NOT NULL)という検査制約と機能的には同等ですが、PostgreSQLでは、明示的に非NULL制約を作成する方がより効果的です。 このように作成された非NULL制約に明示的な名前を付けられないのが欠点です。

もちろん、1つの列に複数の制約を適用することもできます。 そのためには、次々と制約を書いていくだけです。

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric NOT NULL CHECK (price > 0)
);

順序は関係ありません。 書かれた順序と検査される順序は必ずしも同じではありません。

NOT NULL制約に対し、逆のパターンであるNULL制約があります。 これは、列がNULLでなければならないということではありません。 そのような制約は意味がありません。 この制約は、列がNULLであってもよいというデフォルトの振舞いを選択するだけのものです。 NULL制約は標準SQLに存在しませんので、移植予定のアプリケーションで使用すべきではありません。 (これは、PostgreSQLと他の一部のデータベースシステムとの互換性のために追加された機能に過ぎません。) もっとも、スクリプトファイルでの制約の切り替えが簡単であるという理由でこの機能を歓迎するユーザもいます。 例えば、最初に

CREATE TABLE products (
    product_no integer NULL,
    name text NULL,
    price numeric NULL
);

と書いてから、必要な場所にNOTキーワードを挿入できます。

ヒント

ほとんどのデータベース設計において、列の多くをNOT NULLとマークする必要があります。

5.4.3. 一意性制約 #

一意性制約によって、列あるいは列のグループに含まれるデータが、テーブル内の全ての行で一意であることを確実にします。 列制約の場合の構文は以下のとおりです。

CREATE TABLE products (
    product_no integer UNIQUE,
    name text,
    price numeric
);

また、テーブル制約の場合の構文は

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    UNIQUE (product_no)
);

となります。

列の集合に対して一意性制約を定義するには、列名をカンマで区切り、テーブル制約として記述します。

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);

これは、指定された列の値の組み合わせがテーブル全体で一意であることを指定しています。 しかし、列の片方が一意である必要はありません(通常一意ではありません)。

一意性制約には、通常の方法で名前を割り当てることもできます。

CREATE TABLE products (
    product_no integer CONSTRAINT must_be_different UNIQUE,
    name text,
    price numeric
);

一意性制約を追加すると、制約で指定された列または列のグループに対して一意的なBツリーのインデックスが自動的に作られます。 一部の行だけに適用される一意性の制限を一意性制約として作成することはできませんが、そのような制限を一意な部分インデックスを作成することで実現することは可能です。

一般的には、制約に含まれるすべての列の値が等しい複数の行がテーブルの中にある場合に、一意性制約に違反します。 この比較において、デフォルトでは2つのNULL値は等しくないとみなされます。 つまり、一意性制約が存在する場合でも、制約が適用される列の少なくとも1つにNULL値を含むような重複行が格納できるということです。 この動作は、次のようにNULLS NOT DISTINCT句を追加することで変更できます。

CREATE TABLE products (
    product_no integer UNIQUE NULLS NOT DISTINCT,
    name text,
    price numeric
);

または

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric,
    UNIQUE NULLS NOT DISTINCT (product_no)
);

デフォルトの動作は、NULLS DISTINCTを使用して明示的に指定できます。 標準SQLによれば、一意性制約でのデフォルトのNULL処理は実装依存で、他の実装では動作が異なります。 そのため、移植可能なアプリケーションを開発する際には注意が必要です。

5.4.4. 主キー #

主キー制約は、列または列のグループがテーブル内の行を一意に識別するものとして利用できることを意味します。 これには値が一意で、かつNULLでないことが必要となります。 つまり、次の2つのテーブル定義は同じデータを受け入れます。

CREATE TABLE products (
    product_no integer UNIQUE NOT NULL,
    name text,
    price numeric
);

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

主キーも複数の列に渡ることがあり、その構文は一意性制約に似ています。

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    PRIMARY KEY (a, c)
);

主キーを追加すると、主キーで指定された列または列のグループに対して一意的なBツリーのインデックスが自動的に作られます。 また、その列についてNOT NULLの印が強制されます。

1つのテーブルは最大1つの主キーを持つことができます。 (一意性制約および非NULL制約に個数の制限はありません。 機能的にはほとんど同じものですが、主キーとして識別される制約は1つのみです。) リレーショナルデータベース理論では、全てのテーブルに主キーが1つ必要とされています。 この規則はPostgreSQLでは強制されませんが、たいていの場合はこれに従うことが推奨されます。

主キーは文書化、および、クライアントアプリケーションの両方の面で役に立ちます。 例えば、行値の変更が可能なGUIアプリケーションが行を一意的に特定するためには、 おそらくテーブルの主キーを知る必要があります。 他にも主キーが宣言されているときにデータベースシステムがそれを利用する場面がいくつかあります。 例えば、外部キーがそのテーブルを参照するとき、主キーがデフォルトの対象列となります。

5.4.5. 外部キー #

外部キー制約は、列(または列のグループ)の値が、他のテーブルの行の値と一致しなければならないことを指定します。 これによって関連する2つのテーブルの参照整合性が維持されます。

これまで何度か例に使用したproductsテーブルについて考えてみます。

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

また、これらの製品に対する注文を格納するテーブルも作成済みだとしましょう。 この注文のordersテーブルには実際に存在する製品の注文のみを格納したいと思っています。 そこで、productsテーブルを参照するordersテーブルに外部キー制約を定義します。

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    product_no integer REFERENCES products (product_no),
    quantity integer
);

これで、productsテーブルに存在しない非NULLのproduct_no項目を使用して注文を作成することはできなくなります。

このような場合に、ordersテーブルのことを参照テーブル、productテーブルのことを被参照テーブルと呼びます。 同様に、参照列と被参照列もあります。

上記のコマンドは、次のように短縮することもできます。

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    product_no integer REFERENCES products,
    quantity integer
);

列リストがないため、被参照テーブルの主キーが被参照列として使用されます。

外部キー制約には、通常の方法で名前を割り当てることもできます。

外部キーでも、列のグループを制約したり参照したりすることもできます。 これもまた、テーブル制約の形式で記述する必要があります。 以下は、説明のための非現実的な例です。

CREATE TABLE t1 (
  a integer PRIMARY KEY,
  b integer,
  c integer,
  FOREIGN KEY (b, c) REFERENCES other_table (c1, c2)
);

もちろん、制約される列数および型は、被参照列の数および型と一致しなければなりません。

時には外部キー制約の他のテーブルを同じテーブルにすることが有用です。 これは自己参照外部キーと呼ばれます。 たとえばテーブルの行がツリー構造のノードを表現するようにしたいのであれば、以下のように書くことができるでしょう。

CREATE TABLE tree (
    node_id integer PRIMARY KEY,
    parent_id integer REFERENCES tree,
    name text,
    ...
);

トップレベルのノードはparent_idがNULLなのに対し、非NULLのparent_idはテーブルの有効な行を参照するように制約されることになります。

テーブルは複数の外部キー制約を持つことができます。 このことはテーブル間の多対多関係を実装するために使用されます。 例えば、製品と注文に関するそれぞれのテーブルがある場合に、複数の製品にまたがる注文を可能にしたいとします (上の例の構造では不可能です)。 この場合、次のテーブル構造を使用できます。

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    ...
);

CREATE TABLE order_items (
    product_no integer REFERENCES products,
    order_id integer REFERENCES orders,
    quantity integer,
    PRIMARY KEY (product_no, order_id)
);

最後のテーブルで、主キーと外部キーが重なっていることに注目してください。

外部キーが製品に関連付けられていない注文の作成を許可しないことは、既に説明した通りです。 しかし、ある注文で参照していた製品が、注文後に削除されたらどうなるでしょう。 SQLではこのような場合も扱うことができます。 直感的に、いくつかのオプションが考えられます。

  • 参照される製品の削除を許可しない

  • 注文も一緒に削除する

  • 他にもありますか?

具体例として、上の例の多対多関係に次のポリシーを実装してみましょう。 (order_itemsによって)注文で参照されたままの製品を削除しようとしても、この操作を行えないようにします。 注文が削除されると、注文項目も削除されます。

CREATE TABLE products (
    product_no integer PRIMARY KEY,
    name text,
    price numeric
);

CREATE TABLE orders (
    order_id integer PRIMARY KEY,
    shipping_address text,
    ...
);

CREATE TABLE order_items (
    product_no integer REFERENCES products ON DELETE RESTRICT,
    order_id integer REFERENCES orders ON DELETE CASCADE,
    quantity integer,
    PRIMARY KEY (product_no, order_id)
);

削除の制限およびカスケードという2つは、最も一般的なオプションです。 RESTRICTは、被参照行が削除されるのを防ぎます。 NO ACTIONは、制約が検査された時に参照行がまだ存在していた場合に、エラーとなることを意味しています。 これは、何も指定しない場合のデフォルトの振舞いとなります (これらの本質的な違いは、NO ACTIONでは検査をトランザクション中で後回しにできるのに対し、RESTRICTでは後回しにできないということです)。 CASCADEは被参照行が削除された時、それを参照する行も同様に削除されることを指定します。 他にも2つのオプション、SET NULLSET DEFAULTがあります。 これらは、被参照行が削除された際に、参照行の参照列がそれぞれNULLか各列のデフォルト値に設定されます。 これらは制約を守ることを免除することではない、ということに注意してください。 例えば、動作にSET DEFAULTを指定したとしても、デフォルト値が外部キー制約を満たさない場合には操作は失敗します。

ON DELETEアクションの適切な選択は、関連するテーブルが表すオブジェクトの種類によって異なります。 参照元テーブルが、参照先テーブルによって表されるオブジェクトのコンポーネントであり、独立して存在できないものを表している場合は、CASCADEが適切です。 2つのテーブルが独立したオブジェクトを表している場合は、RESTRICTまたはNO ACTIONが適切です。 実際に両方のオブジェクトを削除するアプリケーションは、このことを明示的に指定し、2つの削除コマンドを実行する必要があります。 前述の例では、受注アイテムは受注の一部であり、受注が削除された場合に自動的に削除されるようにすると便利です。 ただし、製品と受注は異なるため、製品を削除すると一部の受注アイテムが自動的に削除されてしまうのは問題となると考えられます。 外部キー関係がオプションの情報を表す場合は、アクションSET NULLまたはSET DEFAULTが適切です。 たとえば、製品テーブルに製品マネージャへの参照が含まれていて、製品マネージャのエントリが削除された場合、製品の製品マネージャをNULLまたはデフォルトに設定すると便利です。

アクションSET NULLおよびSET DEFAULTでは、列リストを使用して、設定する列を指定できます。 通常、外部キー制約のすべての列が設定されます。 サブセットのみを設定すると、特殊な場合に役立ちます。次の例を見てください。

CREATE TABLE tenants (
    tenant_id integer PRIMARY KEY
);

CREATE TABLE users (
    tenant_id integer REFERENCES tenants ON DELETE CASCADE,
    user_id integer NOT NULL,
    PRIMARY KEY (tenant_id, user_id)
);

CREATE TABLE posts (
    tenant_id integer REFERENCES tenants ON DELETE CASCADE,
    post_id integer NOT NULL,
    author_id integer,
    PRIMARY KEY (tenant_id, post_id),
    FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
);

列が指定されていない場合、外部キーもtenant_id列をnullに設定しますが、この列は主キーの一部として必要です。

ON DELETEについても同様で、被参照列が変更(更新)された時に呼び出されるON UPDATEもあります。 列リストがSET NULLSET DEFAULTで指定できない点を除き、可能なアクションは同じです。 この場合、CASCADEは被参照列の更新後の値が参照行にコピーされることを意味します。

通常、参照行はその参照列のいずれかがnullの場合は外部キー制約を満たす必要がありません。 もしMATCH FULLが外部キー宣言に追加された場合、その参照列の全てがnullの場合にのみ参照行は制約を満たすことから逃れることができます(つまりnullと非nullの組み合わせはMATCH FULL制約に違反することが保証されます)。 もし参照行が外部キー制約を満たさない可能性を排除したい場合は、参照列をNOT NULLとして宣言してください。

外部キーは、主キー、または一意性制約を構成する列、または部分インデックスではない一意性インデックスを構成する列全体を参照しなければなりません。 これは、被参照列は常にインデックスがあり、参照する行に一致するかどうかを効率的に検索できることを意味します。 被参照テーブルからの行のDELETEや被参照行のUPDATEは、古い値と一致する行に対して参照テーブルのスキャンが必要となるので、参照行にもインデックスを付けるのは大抵は良い考えです。 これは常に必要という訳ではなく、また、インデックスの方法には多くの選択肢がありますので、外部キー制約の宣言では参照列のインデックスが自動的に作られるということはありません。

データの更新および削除について詳しくは、第6章を参照してください。 また、CREATE TABLEのリファレンス文書にある外部キー制約構文の説明も参照してください。

5.4.6. 排他制約 #

排他制約によって、2つの行に関して指定された列もしくは式を指定された演算子を利用して比較した場合に、少なくとも演算子の比較の1つが偽もしくはnullを返すことを確実にします。 構文は以下のとおりです。

CREATE TABLE circles (
    c circle,
    EXCLUDE USING gist (c WITH &&)
);

詳細はCREATE TABLE ... CONSTRAINT ... EXCLUDEを参照してください。

排他制約を追加すると、制約宣言で指定された種類のインデックスが自動的に作られます。