タイトル: 制約
SEOタイトル: SQL 制約(NOT NULL / UNIQUE / PRIMARY KEY / FOREIGN KEY / CHECK / DEFAULT 完全ガイド)
| この記事の要点 |
|
制約 (Constraint) とは
SQL の制約は、テーブルのカラムに格納できる値のルールを宣言する仕組みです。アプリ側のバリデーションだけに頼ると「別経路から INSERT したらゴミデータが入った」という事故が起きますが、DB 側で制約を貼っておけばどんな経路でも不正データは弾かれます。整合性の最後の砦という位置付けです。
SQL 標準で定義されている主要な制約は次の 6 種類です。
| 制約 | 意味 | 典型用途 |
|---|---|---|
NOT NULL | NULL を許さない | 名前、ステータス等の必須項目 |
UNIQUE | 重複を許さない(NULL は許す) | メールアドレス、社員番号 |
PRIMARY KEY | 主キー(NOT NULL + UNIQUE) | テーブルの一意識別子 |
FOREIGN KEY | 他テーブルの値しか入れられない | 注文.顧客ID → 顧客.ID |
CHECK | 任意の式を真にする値だけ許可 | 年齢 >= 0、ステータス IN (...) |
DEFAULT | 未指定時の既定値 | created_at = NOW() |
NOT NULL — 必須項目
カラムに NULL を入れさせない制約です。必須入力項目に貼ります。
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
INSERT INTO users (id, name) VALUES (1, 'Alice');
-- ERROR: email に NULL を入れようとした
UNIQUE — 重複禁止
カラムの値が重複しないことを保証します。NULL は重複として扱われない(複数の NULL が許される)点が PRIMARY KEY との大きな違いです。
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE
);
-- 複合 UNIQUE (組み合わせで一意)
CREATE TABLE enrollments (
student_id INT,
course_id INT,
CONSTRAINT uq_enroll UNIQUE (student_id, course_id)
);
PRIMARY KEY — 主キー
テーブルの一意識別子です。中身は NOT NULL + UNIQUE ですが、1 テーブルに 1 つしか定義できないのが UNIQUE との違いです。RDBMS は主キーに自動でクラスタインデックスを作るので検索も高速です。
-- 単一カラム主キー
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100)
);
-- 複合主キー
CREATE TABLE order_items (
order_id INT,
item_id INT,
qty INT,
PRIMARY KEY (order_id, item_id)
);
FOREIGN KEY — 参照整合性
他テーブルに存在する値しか入れさせない制約です。「存在しない顧客 ID で注文が作られる」事故を防ぎます。
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
ON DELETE / ON UPDATE で親側削除・更新時の挙動を選べます。
| オプション | 挙動 |
|---|---|
RESTRICT / NO ACTION | 子に参照が残っていたら親の削除/更新を拒否(既定) |
CASCADE | 親と一緒に子も削除/更新 |
SET NULL | 子の外部キー列を NULL にする(子側 NOT NULL なら不可) |
SET DEFAULT | 子の外部キー列を DEFAULT 値にする |
CHECK — 任意条件
カラムの値が満たすべき論理式を直接書ける制約です。MySQL では 8.0.16 から実効、それ以前は構文を受け付けるが無視される点に注意。
CREATE TABLE employees (
id INT PRIMARY KEY,
age INT CHECK (age >= 18 AND age <= 70),
status VARCHAR(10) CHECK (status IN ('active','retired','leave'))
);
DEFAULT — 既定値
INSERT 時に値が指定されなかった場合の既定値を定めます。created_at に CURRENT_TIMESTAMP を入れるのが典型。
CREATE TABLE posts (
id INT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
status VARCHAR(10) NOT NULL DEFAULT 'draft',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
後から追加する — ALTER TABLE
運用中のテーブルに制約を追加するときは ALTER TABLE。名前を付けておくと後で DROP しやすくなります。
-- 追加
ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
-- 削除
ALTER TABLE users DROP CONSTRAINT uq_users_email;
ALTER TABLE orders DROP FOREIGN KEY fk_orders_customer; -- MySQL
よくある質問
Q: アプリ側のバリデーションがあれば DB 制約は不要では?
A: 不要ではありません。バッチ処理、別経路の API、手動 SQL など「アプリを通らない」経路は必ず出てきます。DB 制約は最後の砦として常に貼っておくのが定石です。
Q: 複合 UNIQUE と単一 UNIQUE を両方貼れる?
A: 可能。例えば email 単独で UNIQUE かつ (tenant_id, email) でも UNIQUE のような重複定義も問題ありません。
Q: 外部キーを貼るとパフォーマンスが落ちる?
A: INSERT / UPDATE 時に親テーブル参照のコストが乗りますが、整合性のメリットの方が圧倒的に大きいです。親側カラムにインデックスがあればコストは最小限。