5.3. 制約

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

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

5.3.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制約を使用することができます。

5.3.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.3.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
);

一般に、制約の対象となる列について同じ値を持つ行が、テーブル内に2行以上ある場合は、一意性制約違反になります。 しかし、この比較では2つのNULL値は等価とはみなされません。 つまり、一意性制約があったとしても、制約対象の列の少なくとも1つにNULL値を持つ行を複数格納することができるということです。 この振舞いは標準SQLに準拠していますが、この規則に従わないSQLデータベースもあるそうです。 ですから、移植する予定のアプリケーションを開発する際には注意してください。

5.3.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)
);

プライマリキーは、列または列のグループを、テーブル内で行の一意の識別子として使用できることを示します (このことは、プライマリキーの定義からの直接の帰結です。 一意性制約ではNULL値を除外しないため、一意の識別子が提供されないことに注意してください)。 これは文書化、および、クライアントアプリケーションの両方の面で役に立ちます。 例えば、行値の変更が可能なGUIアプリケーションが行を一意的に特定するためには、おそらくテーブルのプライマリキーを知る必要があります。

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

5.3.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テーブルに存在しない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 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に似たもので、被参照列が変更(更新)された時に呼び出されるON UPDATEもあります。 これらが行えるアクションは同じです。

データの更新および削除について詳しくは、第6章を参照してください。

最後に、外部キーはプライマリキーまたは一意性制約のいずれかである列を参照している必要があります。 外部キーが一意性制約を参照している場合、NULL値をどのようにマッチさせるかに関する他の方法がいくつかあります。 これらについては、リファレンス文書のCREATE TABLEに説明があります。