シェア

MySQLのカラムとテーブル制約とは?

制約は、カラムまたはテーブルで有効な値を定義するユーザー定義の要件です。制約は、データ型が許可するよりも厳密に許容値を絞り込むための追加の制限と考えることができます。

制約を使用すると、すべてのエントリが持つ必要のある品質を定義でき、サーバー自体がデータ入力または更新時に制限を適用します。例として、さまざまな物質の沸点を表すカラムが、その凝固点よりも低くなるのは意味がない場合があります。制約は、型ではできない場合でも、このタイプの要件を強制できます。

制約が定義される場所:カラム制約とテーブル制約

MySQLでは、特定のカラムまたはテーブル全般に関連付けられた制約を作成できます。

ほとんどすべての制約は、変更なしで両方の形式で使用できます

制約カラムテーブル
CHECKはいはい
NOT NULLはいいいえ*
UNIQUEはいはい
PRIMARY KEYはいはい
FOREIGN KEYいいえはい

*NOT NULLはテーブル制約として使用できません。ただし、CHECKテーブル制約内のステートメントとしてIS NOT NULLを使用することで、結果を近似できます。

カラム制約とテーブル制約の違いを見てみましょう。

カラム制約

カラム制約は、単一のカラムに関連付けられた制約です。これらは、カラムに提案された値が有効かどうかを判断するために使用されます。カラム制約は、入力が基本的な型要件(intカラムの数値が整数であることを確認するなど)に対して検証された後に評価されます。

カラム制約は、単一のフィールドに限定された要件を表現するのに最適です。制約条件を関係するカラムに直接アタッチします。たとえば、personテーブルのage制限を、カラム名とデータ型の後に制約を追加することでモデル化できます。

CREATE TABLE person (
. . .
age INT CHECK (age >= 0),
. . .
);

このスニペットは、カラムの1つがageというintであるpersonテーブルを定義します。ageは0以上である必要があります。カラム制約は、影響を与えるカラムに追加の要件として追加されるため、理解しやすいです。

テーブル制約

もう一方のタイプの制約は、テーブル制約と呼ばれます。テーブル制約は、カラム制約ができるほとんどすべての制限を表現できますが、さらに複数のカラムを含む制限を表現できます。テーブル制約は、特定のカラムに関連付けられるのではなく、テーブルの個別のコンポーネントとして定義され、テーブルの任意のカラムを参照できます。

前に見たカラム制約は、次のようにテーブル制約として表現できます。

CREATE TABLE person (
. . .
age INT,
. . .
CHECK (age >= 0)
);

同じ基本的な構文が使用されますが、制約は別々にリストされます。テーブル制約が複合制限を導入する機能を活用するために、論理AND演算子を使用して、異なるカラムからの複数の条件を結合できます。

たとえば、銀行データベースでは、qualified_borrowersというテーブルで、個人がローンを受ける資格を得るために既存の口座を持っているかどうか、および担保を提供できるかどうかを確認する必要がある場合があります。これらを同じチェックに含めるのが理にかなっている場合があります。

CREATE TABLE qualified_borrowers (
. . .
account_number INT,
acceptable_collateral BOOLEAN,
. . .
CHECK (account_number IS NOT NULL AND acceptable_collateral = true)
);

ここでは、CHECK制約を再度使用して、account_numberがnullではなく、融資担当者がクライアントをacceptable_collateralカラムをチェックすることで許容可能な担保を持っているとマークしていることを確認します。複数のカラムがチェックされているため、テーブル制約が必要です。

これらの例では主にCREATE TABLE SQLコマンドを使用して新しいテーブルを作成しますが、ALTER TABLEを使用して既存のテーブルに制約を追加することもできます。 ALTER TABLEを使用すると、新しい制約により、テーブル内の現在の値が新しい制約に対してチェックされます。値が制約に違反している場合、制約を追加できません。

制約の名前の作成

デフォルトの制約名

上記の構文を使用して制約を作成すると、MySQLは妥当ですが曖昧な名前を自動的に選択します。上記のqualified_borrowersテーブルの場合、MySQLは制約にqualified_borrowers_chk_1という名前を付けます。

INSERT INTO qualified_borrowers VALUES (123, false);
ERROR 3819 (HY000): Check constraint 'qualified_borrowers_chk_1' is violated.

この名前は、制約が違反した場合に、テーブルと制約のタイプに関する情報を提供します。ただし、テーブルに複数の制約が存在する場合、トラブルシューティングに役立つように、より説明的な名前が役立ちます。

カスタム制約名

制約定義の前にCONSTRAINTキーワードとそれに続く名前を付けることで、制約の名前をオプションで指定できます。

カスタム名を追加するための基本的な構文は次のとおりです。

CONSTRAINT <constraint_name> <constraint_type_and_details>

たとえば、qualified_borrowersテーブルの制約にloan_worthinessという名前を付けたい場合は、代わりに次のようにテーブルを定義できます。

CREATE TABLE qualified_borrowers (
. . .
account_number INT,
acceptable_collateral BOOLEAN,
. . .
CONSTRAINT loan_worthiness CHECK (account_number IS NOT NULL AND acceptable_collateral = true)
);

これで、制約に違反すると、より説明的なラベルが表示されます。

INSERT INTO qualified_borrowers VALUES (123, false);
ERROR 3819 (HY000): Check constraint 'loan_worthiness' is violated.

カラム制約にも同じ方法で名前を付けることができます。

CREATE TABLE teenagers (
. . .
age INT CONSTRAINT is_teenager CHECK (age >= 13 AND age <= 19),
. . .
);

MySQLで使用可能な制約のリスト

制約の基本的な仕組みについて説明したので、使用可能な制約とその使用方法について詳しく見ていきましょう。

チェック制約

チェック制約は、カラム値またはテーブル値を含む式をブール値として評価することを指定できる汎用制約です。

チェック制約の例はすでにいくつか見てきました。チェック制約は、キーワードCHECKで始まり、括弧で囲まれた式を提供します。カラム制約の場合、これはデータ型宣言の後に配置されます。テーブル制約の場合、これらは対話するカラムが定義された後であればどこにでも配置できます。

たとえば、2019年の長編賞にノミネートされ、資格のある映画を含むfilm_nominationsテーブルを作成できます。

CREATE TABLE film_nominations (
title VARCHAR(250),
director VARCHAR(250),
release_date DATE CHECK ('2019-01-01' <= release_date AND release_date <= '2019-12-31'),
length INT,
votes INT,
CHECK (votes >= 10 AND length >= 40)
);

1つのカラムチェック制約があり、release_dateが2019年内であることをチェックします。その後、テーブルチェック制約があり、映画がノミネートされるのに十分な票を獲得し、長さが「長編」カテゴリの資格があることを確認します。

チェック制約を評価する場合、許容値はtrueとして評価されます。新しいレコードの値がすべての型要件と制約を満たしている場合、レコードはテーブルに追加されます。

INSERT INTO film_nominations VALUES (
'A great film',
'Talented director',
'2019-07-16',
117,
45
);
Query OK, 1 row affected (0.01 sec)

falseと評価される値は、制約が満たされていないことを示すエラーを生成します。

INSERT INTO film_nominations VALUES (
'A poor film',
'Misguided director',
'2019-10-24',
128,
1
);
ERROR 3819 (HY000): Check constraint 'film_nominations_chk_2' is violated.

この場合、映画は必要な票数を除いて、すべての条件を満たしています。MySQLは、最後のテーブルチェック制約に合格しないため、送信を拒否します。

非NULL制約

NOT NULL制約は、はるかに焦点を絞っています。カラム内の値がNULLでないことを保証します。これは単純な制約ですが、非常に頻繁に使用されます。

MySQLで非NULL制約を追加する方法

カラムを非NULL値を必須とするようにマークするには、型宣言の後にNOT NULLを追加します。

CREATE TABLE national_capitals (
country VARCHAR(250) NOT NULL,
capital VARCHAR(250) NOT NULL
);

上記の例では、国とその首都をマッピングする単純な2カラムのテーブルがあります。これらは両方とも空白のままにするのは意味がない必須フィールドであるため、NOT NULL制約を追加します。

NULL値を挿入すると、エラーが発生します。

INSERT INTO national_capitals VALUES (
NULL,
'London',
);
ERROR 1048 (23000): Column 'country' cannot be null

NOT NULL制約は、カラム制約としてのみ機能します(テーブル制約として使用することはできません)。ただし、テーブルCHECK制約内でIS NOT NULLを使用することで、これを簡単に回避できます。

たとえば、これはテーブル制約を使用して同等の保証を提供します。

CREATE TABLE national_capitals (
country VARCHAR(250),
capital VARCHAR(250),
CHECK (country IS NOT NULL AND capital IS NOT NULL)
);

ユニーク制約

UNIQUE制約は、カラム内の各値が繰り返されてはならないことをMySQLに指示します。これは、複数のレコードで同じ値を持つことが不可能である必要がある多くの異なるシナリオで役立ちます。

たとえば、あらゆる種類のIDを扱うカラムは、定義上、一意の値を持つ必要があります。社会保障番号、学生または顧客ID、または製品UPC(バーコード番号)は、特定の人またはアイテムを区別できない場合、役に立たなくなります。

UNIQUE制約は、カラムレベルで指定できます。

CREATE TABLE supplies (
supply_id INT UNIQUE,
name VARCHAR(250),
inventory INT
);

テーブル制約として指定することもできます。

CREATE TABLE supplies (
supply_id INT,
name VARCHAR(250),
inventory INT,
UNIQUE (supply_id)
);

UNIQUEテーブル制約を使用する利点の1つは、カラムの組み合わせに対して一意性チェックを実行できることです。これは、MySQLが一緒に評価する必要がある2つ以上のカラムを指定することで機能します。個々のカラムの値は繰り返される可能性がありますが、指定された値の組み合わせは一意である必要があります。

例として、前に使用したnational_capitalsテーブルを振り返ってみましょう。

CREATE TABLE national_capitals (
country VARCHAR(250) NOT NULL,
capital VARCHAR(250) NOT NULL,
);

同じペアに対して複数のレコードを追加しないようにしたい場合、ここにUNIQUE制約をカラムに追加できます。

CREATE TABLE national_capitals (
country VARCHAR(250) NOT NULL UNIQUE,
capital VARCHAR(250) NOT NULL UNIQUE
);

これにより、国と首都の両方が各テーブルに1回だけ存在することが保証されます。ただし、一部の国には複数の首都があります。これは、同じcountry値を持つ複数のエントリが存在する可能性があることを意味します。これらは現在の設計では機能しません。

INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'La Paz'
);
ERROR 1062 (23000): Duplicate entry 'Bolivia' for key 'national_capitals.country'

個々のカラムで繰り返される値を許可しながら、重複エントリが発生しないようにしたい場合は、countrycapitalの組み合わせに対する一意性チェックで十分です。

CREATE TABLE national_capitals (
country VARCHAR(250),
capital VARCHAR(250),
UNIQUE (country, capital)
);

これで、ボリビアの首都の両方をエラーなしでテーブルに追加できます。

INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'La Paz'
);
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)

ただし、同じ組み合わせを2回追加しようとすると、制約によってキャッチされます。

INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
INSERT INTO national_capitals VALUES (
'Bolivia',
'Sucre'
);
Query OK, 1 row affected (0.00 sec)
ERROR 1062 (23000): Duplicate entry 'Bolivia-Sucre' for key 'national_capitals.country'

主キー制約

PRIMARY KEY制約は、特別な目的を果たします。これは、カラムをテーブル内のレコードを一意に識別するために使用できることを示します。つまり、確実に一意である必要があり、すべてのレコードがそのカラムに値を持っている必要があります。

主キーはすべてのテーブルに推奨されますが、必須ではなく、すべてのテーブルは1つの主キーしか持つことができません。主キーは主に、テーブル内の個々のレコードを識別、取得、変更、または削除するために使用されます。これにより、ユーザーと管理者は、MySQLによって正確に1つのレコードに一致することが保証されている識別子を使用して操作をターゲットにすることができます。

前に見たsuppliesテーブルを例として使用しましょう。

CREATE TABLE supplies (
supply_id INT UNIQUE,
name VARCHAR(250),
inventory INT
);

ここでは、supply_idが一意である必要があることを特定しました。このカラムを主キーとして使用する場合(一意性と非NULL値を保証)、UNIQUE制約をPRIMARY KEYに変更するだけで済みます。

CREATE TABLE supplies (
supply_id INT PRIMARY KEY,
name VARCHAR(250),
inventory INT
);

このようにして、特定のサプライの在庫量を更新する必要がある場合は、主キーを使用してターゲットにすることができます。

INSERT INTO supplies VALUES (
38,
'nails',
5
);
UPDATE supplies set inventory = 10 WHERE supply_id = 38;
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

多くのテーブルでは単一のカラムを主キーとして使用しますが、テーブル制約としてカラムのセットを使用して主キーを作成することも可能です。

national_capitalsテーブルは、これを実証するのに適した候補です。既存のカラムを使用して主キーを作成したい場合は、UNIQUEテーブル制約をPRIMARY KEYに置き換えることができます。

CREATE TABLE national_capitals (
country VARCHAR(250),
capital VARCHAR(250),
PRIMARY KEY (country, capital)
);

外部キー制約

外部キーは、あるテーブル内の別のテーブル内のカラム値を参照するカラムです。これは、テーブルが関連データを含むさまざまなシナリオで望ましく、多くの場合必要です。データベースが個別のテーブルに格納されたデータを簡単に接続および参照できるこの機能は、リレーショナルデータベースの主要な機能の1つです。

たとえば、個々の注文を追跡するためのordersテーブルと、顧客の連絡先情報と情報を追跡するためのcustomersテーブルがある場合があります。顧客は多くの注文を持つ可能性があるため、この情報を別々に配置するのが理にかなっています。ただし、これらの2つのテーブルのレコードを簡単にリンクして、より複雑な操作を可能にすることも理にかなっています。

MySQLで外部キー制約を作成する方法

まず、customersテーブルをモデル化してみましょう。

CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
first_name VARCHAR(250),
last_name VARCHAR(250),
phone_number BIGINT,
);

このテーブルは非常にシンプルです。顧客のファーストネーム、ラストネーム、電話番号を格納するカラムが含まれています。また、PRIMARY KEY制約を使用するIDカラムも指定します。serialエイリアスは、IDが指定されていない場合にシーケンス内の次のIDを自動的に生成するために使用されます。

ordersテーブルの場合、個々の注文に関する情報を指定できるようにする必要があります。不可欠なデータの1つは、どの顧客が注文したかです。外部キーを使用して、情報を複製せずに注文を顧客にリンクできます。FOREIGN KEY制約を使用してこれを行います。これは、別のテーブルのカラムへの外部キーリレーションシップを定義します。

CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
order_date DATE,
customer BIGINT UNSIGNED,
FOREIGN KEY (customer) REFERENCES customers(customer_id)
);

ここでは、ordersテーブルのcustomerカラムが、customersテーブルのcustomer_idカラムとの外部キーリレーションシップを持っていることを示しています。

外部キーカラムの型が、外部テーブルで使用されている型と互換性があることを確認する必要があります。customersテーブルのcustomer_idカラムは、SERIALエイリアスを使用します。これは、BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUEを表すため、ordersテーブルのcustomerカラムのデータ型としてBIGINT UNSIGNEDを使用して一致させることができます。

有効な顧客を参照していない値をordersテーブルに挿入しようとすると、MySQLはそれを拒否します。

INSERT INTO orders VALUES (
100,
'2019-11-19',
300
);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`prisma`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer`) REFERENCES `customers` (`customer_id`))

最初に顧客を追加すると、注文はシステムによって受け入れられます。

INSERT INTO customers VALUES (
300,
'Jill',
'Smith',
5551235677
);
INSERT INTO orders VALUES (
100,
'2019-11-19',
300
);
Query OK, 1 row affected (0.00 sec)
Query OK, 1 row affected (0.00 sec)

主キーは、1つのレコードのみに一致することを保証するため、外部キーの優れた候補ですが、一意である限り、他のカラムを外部キーとして使用することもできます。

一意であることが保証されているカラムのセットを使用することもできます。

CREATE TABLE example (
. . .
FOREIGN KEY (column1, column2) REFERENCES other_table (column1, column2)
);

削除または更新時に外部キーをどうするかを決定する

外部キー制約を定義するときに検討する必要があることの1つは、参照テーブルの値が削除または更新された場合にどうするかです。

例として、customersテーブルとordersテーブルを再度見てみましょう。ordersテーブルに関連付けられた注文がある場合に、customersテーブルから顧客を削除するときにシステムがどのように応答するかを指定する必要があります。

次のオプションから選択できます。

  • RESTRICT:削除を制限することを選択すると、ordersテーブルのレコードによって参照されている場合、MySQLはcustomerレコードの削除を拒否することを意味します。顧客を削除するには、最初に関連するレコードをordersテーブルから削除する必要があります。その後でのみ、顧客テーブルから値を削除できます。これがデフォルトのアクションです。
  • CASCADE: cascadeオプションを選択すると、customerレコードを削除した際に、ordersテーブル内でそれを参照しているレコードも一緒に削除されることを意味します。これは多くの場合に役立ちますが、誤ってデータを削除しないように注意して使用する必要があります。
  • NO ACTION: 他のデータベースシステムではNO ACTIONオプションでチェックを遅延させることができますが、MySQLではこれはRESTRICTと同等です。システムは更新または削除リクエストを拒否します。
  • SET NULL: このオプションは、参照されているレコードが削除されたときに、参照元のカラムをNULLに設定するようにMySQLに指示します。したがって、customersテーブルから顧客を削除すると、ordersテーブルのcustomerカラムはNULLに設定されます。
  • SET DEFAULT: 他のデータベースシステムでは、参照の削除または更新の場合にカラムをデフォルト値に設定できますが、MySQLは実際にはこのアクションを許可しておらず、このオプションを使用してテーブルを定義することはできません。

これらのアクションは、外部キー制約を定義する際に、ON DELETEに続けてアクションを追加することで指定できます。したがって、顧客が削除されたときにシステムから関連する注文を削除したい場合は、次のように指定できます。

CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
order_date DATE,
customer INT,
FOREIGN KEY (customer) REFERENCES customers(customer_id) ON DELETE CASCADE
);

これらのタイプのアクションは、ON DELETEの代わりにON UPDATEを使用することで、参照されているカラムを削除する代わりに更新するときにも適用できます。

結論

このガイドでは、制約とは何か、そして制約がMySQLテーブルに入力されるデータをどのように制御するのに役立つかについて説明しました。カラム制約とテーブル制約の違い、およびテーブル形式を使用することで得られる柔軟性の向上について議論しました。次に、MySQLがサポートする制約の種類と、テーブルでのそれらの使用方法について説明しました。

制約は、テーブルカラムの正確な要件を定義するのに役立ち、そのため、多くのシナリオで不可欠です。さまざまな制約がどのように機能し、どのようなシナリオを防止するのに役立つかを理解することは、データが必要な標準に準拠していることを保証する上で非常に役立ちます。制約が定義されると、MySQLは制約を自動的に適用して、問題が発生する前に防止するのに役立ちます。

著者について
Justin Ellingwood

Justin Ellingwood

Justinは2013年からデータベース、Linux、インフラストラクチャ、および開発者ツールについて執筆しています。彼は現在、妻と2羽のウサギと一緒にベルリンに住んでいます。彼は通常、三人称で書く必要がないので、関係者全員にとって安心です。