データベースを使っていると、「制約(constraint)」という言葉を聞く機会があります。初めて聞いた時は「制約?縛りみたいなもの?」と思うかもしれませんね。
その直感は実は正しいんです。SQL制約とは、データベースに保存できるデータのルールを決める仕組みのことです。たとえば「年齢には負の数を入れない」「メールアドレスは重複しない」といったルールを設定できます。
なぜこんなルールが必要なのでしょうか。それは、間違ったデータが入り込むことを防ぐためです。交通ルールがないと事故が起きやすいのと同じように、データにもルールがないと「おかしなデータ」が入り込んでしまいます。
この記事では、SQLの制約について、種類ごとの使い方から実践的な活用方法まで、初心者の方でも理解できるように分かりやすく解説していきます。
データベース制約の基本を理解しよう

まずは、制約とは何かをもう少し詳しく見ていきましょう。
制約とは「データの品質を守る門番」
制約は、データベースに入ってくるデータをチェックする「門番」のような存在です。ルール違反のデータが来たら、「このデータは入れられません」とブロックします。
たとえば、会員登録システムで「メールアドレスは必須」というルールがあるとしましょう。制約を設定しておけば、メールアドレスが空欄の登録を自動的に拒否してくれます。
プログラム側でチェックすることもできますが、データベース側でも制約を設けることで、二重のガードになるのです。
制約を使うメリット
制約を設定することには、いくつもの利点があります。
データの整合性が保たれる
ルール違反のデータが入り込むことを防ぎ、データベース全体の品質が保たれます。
バグの早期発見
アプリケーションのバグで変なデータを入れようとした時、すぐにエラーで気づけます。
開発効率の向上
データベース側でチェックしてくれるため、アプリケーション側のコードがシンプルになります。
ドキュメントの役割
制約を見れば、そのテーブルにどんなルールがあるか一目で分かります。
制約はいつ設定する?
制約は主に2つのタイミングで設定できます。
テーブル作成時
CREATE TABLE文で、テーブルを作る時に一緒に制約を定義します。これが最も一般的な方法です。
テーブル作成後
ALTER TABLE文で、後から制約を追加することもできます。既存のテーブルにルールを追加したい時に使います。
6つの主要な制約タイプ
SQLには主に6種類の制約があります。それぞれ詳しく見ていきましょう。
1. PRIMARY KEY制約(主キー制約)
PRIMARY KEY制約は、各行を一意に識別するための制約です。「主キー」や「プライマリーキー」と呼ばれます。
特徴
- 値が重複してはいけない(UNIQUE)
- 値が空(NULL)であってはいけない(NOT NULL)
- 一つのテーブルに一つだけ設定できる
使用例
CREATE TABLE users (
user_id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100)
);
この例では、user_idが主キーです。同じuser_idを持つユーザーを2人登録することはできません。
AUTO_INCREMENTとの組み合わせ
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10, 2)
);
AUTO_INCREMENTを使うと、データを追加する度に自動的に番号が増えていきます。IDを手動で考える必要がなくなるため便利です。
複合主キー
複数の列を組み合わせて主キーにすることもできます。
CREATE TABLE order_details (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);
「注文番号と商品番号の組み合わせ」でユニークになるケースです。
2. FOREIGN KEY制約(外部キー制約)
FOREIGN KEY制約は、別のテーブルとの関連を定義する制約です。「外部キー」や「フォーリンキー」と呼ばれます。
特徴
- 参照先のテーブルに存在する値しか入れられない
- データの整合性を保つ
- テーブル間の関係性を明確にする
基本的な使用例
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
この例では、customer_idがcustomersテーブルを参照しています。customersテーブルに存在しない顧客IDは入れられません。
削除時の動作を指定(ON DELETE)
CREATE TABLE reviews (
review_id INT PRIMARY KEY,
product_id INT,
rating INT,
FOREIGN KEY (product_id) REFERENCES products(product_id)
ON DELETE CASCADE
);
ON DELETE CASCADEを指定すると、商品が削除された時に、そのレビューも自動的に削除されます。
主なオプション
CASCADE:親データ削除時に子データも削除SET NULL:親データ削除時に子データをNULLにRESTRICT:子データがある場合は親データを削除できない(デフォルト)NO ACTION:RESTRICTと同じ動作
3. UNIQUE制約(一意性制約)
UNIQUE制約は、列の値が重複しないようにする制約です。PRIMARY KEYと似ていますが、NULLを許可できる点が異なります。
特徴
- 値の重複を許さない
- NULLは許可される(データベースによる)
- 一つのテーブルに複数設定できる
使用例
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE,
name VARCHAR(100)
);
メールアドレスと電話番号が重複しないように制約を設定しています。社員IDは主キーなので、こちらも当然重複しません。
複数列の組み合わせで一意性を保つ
CREATE TABLE class_enrollment (
student_id INT,
course_id INT,
enrollment_date DATE,
UNIQUE (student_id, course_id)
);
「同じ学生が同じコースに重複して登録できない」というルールを実現しています。
4. NOT NULL制約(非NULL制約)
NOT NULL制約は、列に必ず値を入れることを要求する制約です。空欄を許さない設定と言えます。
特徴
- 値が空(NULL)であってはいけない
- 必須項目を定義する
- 最も基本的な制約の一つ
使用例
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
customer_name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
address TEXT
);
顧客名とメールアドレスは必須項目です。電話番号と住所は任意(NULLを許可)になっています。
PRIMARY KEYには自動的にNOT NULLが付く
主キーには、明示的に書かなくてもNOT NULL制約が自動的に付きます。主キーは各行を識別するものなので、空欄では意味がないためです。
5. CHECK制約(チェック制約)
CHECK制約は、列に入れられる値の条件を細かく指定できる制約です。最も柔軟性の高い制約と言えるでしょう。
特徴
- カスタムの条件式を設定できる
- 範囲指定や値の種類を制限できる
- 複雑なビジネスルールを実装できる
数値範囲の制約
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10, 2) CHECK (price >= 0),
stock INT CHECK (stock >= 0)
);
価格と在庫数が負の数にならないようにしています。
複数の条件を組み合わせる
CREATE TABLE employees (
employee_id INT PRIMARY KEY,
name VARCHAR(100),
age INT CHECK (age >= 18 AND age <= 65),
salary DECIMAL(10, 2) CHECK (salary >= 0),
department VARCHAR(50) CHECK (department IN ('営業', '開発', '総務', '人事'))
);
年齢は18歳から65歳の範囲、部門は指定された4つの中から選ぶというルールです。
日付の整合性チェック
CREATE TABLE projects (
project_id INT PRIMARY KEY,
project_name VARCHAR(100),
start_date DATE,
end_date DATE,
CHECK (end_date >= start_date)
);
終了日が開始日より前になることを防いでいます。
データベースごとの違い
CHECK制約の構文は、データベース管理システムによって若干異なる場合があります。MySQL 8.0.16以降、PostgreSQL、SQL Serverなどで使用できます。
6. DEFAULT制約(デフォルト値制約)
DEFAULT制約は、値が指定されなかった時に自動的に設定される値を定義します。厳密には「制約」というより「デフォルト設定」ですが、データの一貫性を保つ重要な機能です。
特徴
- 値が省略された時の初期値を設定
- データ入力の手間を減らす
- 一貫性のあるデータを保つ
基本的な使用例
CREATE TABLE posts (
post_id INT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
status VARCHAR(20) DEFAULT '下書き',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
view_count INT DEFAULT 0
);
投稿のステータスはデフォルトで「下書き」、作成日時は自動的に現在時刻、閲覧数は0から始まります。
関数を使ったデフォルト値
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
registration_date DATE DEFAULT CURRENT_DATE,
last_login TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CURRENT_TIMESTAMPやCURRENT_DATEなどの関数を使えば、動的な値を設定できます。
制約の追加・変更・削除

既存のテーブルに制約を追加したり、削除したりする方法を紹介します。
制約の追加
ALTER TABLE文を使って、後から制約を追加できます。
PRIMARY KEY制約の追加
ALTER TABLE users
ADD PRIMARY KEY (user_id);
FOREIGN KEY制約の追加
ALTER TABLE orders
ADD FOREIGN KEY (customer_id)
REFERENCES customers(customer_id);
UNIQUE制約の追加
ALTER TABLE employees
ADD UNIQUE (email);
CHECK制約の追加
ALTER TABLE products
ADD CHECK (price > 0);
制約に名前を付ける
制約には名前を付けることができます。名前があると、後から管理しやすくなります。
CREATE TABLE orders (
order_id INT,
customer_id INT,
order_date DATE,
total_amount DECIMAL(10, 2),
CONSTRAINT pk_orders PRIMARY KEY (order_id),
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
CONSTRAINT chk_amount CHECK (total_amount >= 0)
);
CONSTRAINT 制約名という形式で名前を付けます。
制約の削除
制約を削除する時も、ALTER TABLE文を使います。
PRIMARY KEY制約の削除
ALTER TABLE users
DROP PRIMARY KEY;
FOREIGN KEY制約の削除
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_customer;
名前付き制約の削除
ALTER TABLE products
DROP CONSTRAINT chk_price;
制約に名前を付けておくと、削除する時に指定しやすくなります。
実践的な制約の使い方
実際のプロジェクトで使える、実践的な例を見ていきましょう。
ECサイトのテーブル設計例
-- 顧客テーブル
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
);
-- 商品テーブル
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL CHECK (price >= 0),
stock INT DEFAULT 0 CHECK (stock >= 0),
category VARCHAR(50) NOT NULL,
is_available BOOLEAN DEFAULT TRUE
);
-- 注文テーブル
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) DEFAULT '処理中'
CHECK (status IN ('処理中', '発送済み', '完了', 'キャンセル')),
total_amount DECIMAL(10, 2) NOT NULL CHECK (total_amount >= 0),
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT
);
-- 注文明細テーブル
CREATE TABLE order_items (
item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
unit_price DECIMAL(10, 2) NOT NULL CHECK (unit_price >= 0),
FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(product_id)
ON DELETE RESTRICT,
UNIQUE (order_id, product_id)
);
この設計では、様々な制約を組み合わせてデータの整合性を保っています。
予約システムのテーブル設計例
-- 会議室テーブル
CREATE TABLE rooms (
room_id INT AUTO_INCREMENT PRIMARY KEY,
room_name VARCHAR(100) NOT NULL UNIQUE,
capacity INT NOT NULL CHECK (capacity > 0),
is_available BOOLEAN DEFAULT TRUE
);
-- 予約テーブル
CREATE TABLE bookings (
booking_id INT AUTO_INCREMENT PRIMARY KEY,
room_id INT NOT NULL,
user_id INT NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
purpose VARCHAR(200),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CHECK (end_time > start_time),
FOREIGN KEY (room_id) REFERENCES rooms(room_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
終了時刻が開始時刻より後になるようCHECK制約を設定しています。
よくある質問と回答
SQL制約について、よくある質問をまとめました。
Q1:制約違反のデータを入れようとするとどうなる?
エラーが発生して、そのデータは保存されません。たとえば、PRIMARY KEY制約があるのに重複した値を入れようとすると、「Duplicate entry(重複エラー)」が表示されます。
アプリケーション側では、このエラーをキャッチして適切なメッセージをユーザーに表示する必要があります。
Q2:制約を無視してデータを入れる方法はある?
基本的にはありません。制約はデータベースの根本的なルールなので、無視することはできません。
どうしても必要な場合は、一時的に制約を無効化するか、削除してからデータを入れて、再び制約を追加する方法がありますが、これは非常にリスクの高い操作です。本番環境では絶対に避けるべきでしょう。
Q3:既にデータが入っているテーブルに制約を追加できる?
できますが、既存のデータが新しい制約のルールに違反していないことが条件です。
たとえば、既に重複したメールアドレスが入っているテーブルに、UNIQUE制約を追加しようとするとエラーになります。まず重複データを修正してから、制約を追加する必要があります。
Q4:制約が多すぎるとパフォーマンスに影響する?
わずかな影響はありますが、通常は問題になりません。制約のチェックは非常に高速です。
むしろ、制約がないことで不正なデータが入り込み、後でデータクレンジングが必要になる方が、パフォーマンス的にも作業的にも大きなコストになります。
ただし、外部キー制約が複雑に絡み合っている場合、大量データの一括挿入や削除で時間がかかることがあります。その場合は一時的に制約を無効化する手法もありますが、慎重に行う必要があります。
Q5:CHECK制約とアプリケーション側のバリデーション、どちらが良い?
両方とも実装するのが理想です。
アプリケーション側のバリデーションは、ユーザーに即座にフィードバックを返せます。一方、データベースの制約は、どんな経路からデータが入ってきても確実にチェックできます。
たとえば、複数のアプリケーションが同じデータベースにアクセスする場合、データベース側の制約が最後の砦になります。
Q6:NULLと空文字列は違うの?
はい、全く違います。
- NULL:値が存在しない、不明、未定義の状態
- 空文字列(”):値は存在するが、その中身が空っぽの状態
たとえば、電話番号が未入力の場合はNULL、住所の「建物名」欄が空欄の場合は空文字列、というように使い分けます。
NOT NULL制約は「NULL禁止」なので、空文字列は入れられます。
制約を使う時のベストプラクティス
制約を効果的に使うためのポイントを紹介します。
1. できるだけテーブル作成時に定義する
後から制約を追加することもできますが、最初から設計に組み込む方が安全です。データが入ってから制約を追加しようとすると、既存データとの整合性で問題が起きることがあります。
2. 制約には分かりやすい名前を付ける
-- 良い例
CONSTRAINT fk_orders_customer
CONSTRAINT chk_price_positive
-- 悪い例(自動生成された名前)
CONSTRAINT FK_3A4B5C6D
名前があると、エラーメッセージが分かりやすくなり、後から管理しやすくなります。
3. ビジネスルールを反映させる
データベースの制約は、単なる技術的な設定ではありません。ビジネス上のルールを反映させる重要な仕組みです。
「注文金額は1円以上」「予約時間は営業時間内」といったビジネスルールを制約で表現しましょう。
4. 制約のドキュメント化
設計書やコメントで、なぜその制約が必要なのか記録しておきます。
-- 注文金額は0円以上である必要がある
-- 理由:無料配布の場合も0円として記録するため
CHECK (total_amount >= 0)
将来のメンテナンスや他のメンバーのために、意図を明確にしておくことが大切です。
5. 外部キーの削除・更新時の動作を慎重に選ぶ
ON DELETE CASCADEは便利ですが、予期しない大量削除を引き起こす可能性があります。ビジネスロジックに応じて適切なオプションを選びましょう。
まとめ:制約でデータベースの品質を守ろう
SQL制約について、理解が深まったでしょうか。
重要なポイントをおさらい
- PRIMARY KEY:各行を一意に識別、重複とNULLを禁止
- FOREIGN KEY:テーブル間の関連を定義、参照整合性を保つ
- UNIQUE:値の重複を禁止、NULLは許可
- NOT NULL:必須項目を定義、空欄を禁止
- CHECK:カスタムの条件を設定、柔軟なルール作成
- DEFAULT:デフォルト値を設定、入力の手間を削減
制約は、データベースの品質を守る重要な仕組みです。最初は少し面倒に感じるかもしれませんが、長期的には以下のメリットがあります。
- バグの早期発見
- データの整合性保証
- メンテナンスの容易さ
- ドキュメントとしての役割
「後で修正すれば良い」と考えて制約なしでスタートすると、不正なデータが蓄積されて、後から直すのが非常に困難になります。
テーブル設計の段階で、適切な制約をしっかり設定しておきましょう。それがデータベース開発の基本であり、プロジェクトの成功につながります!


コメント