データベースで複数のテーブルを扱っていると、「注文テーブルに存在しない顧客IDが入ってしまった」「削除した商品を参照する注文データが残っている」といった問題に遭遇することがあります。
こうしたデータの矛盾を防いでくれるのがFOREIGN KEY制約(外部キー制約)です。
外部キー制約は、テーブル同士の関連性を定義して、データの整合性を自動的に守ってくれる仕組み。正しく設定すれば、データベースの信頼性が大きく向上するんです。
この記事では、FOREIGN KEY制約の基本から実践的な使い方まで、初心者の方にも分かりやすく解説していきます。
FOREIGN KEY制約とは?基本を理解しよう

まずは、FOREIGN KEY制約がどういうものなのか見ていきましょう。
FOREIGN KEY制約の概要
FOREIGN KEY制約(外部キー制約)は、テーブル間の関連性を定義し、データの整合性を保つためのルールです。
例えば、「注文テーブル」と「顧客テーブル」があるとします。注文には必ず顧客が関連付けられているはずですよね。
外部キー制約を設定すると、「注文テーブルに登録できる顧客IDは、顧客テーブルに実際に存在するものだけ」というルールを強制できます。
主キーと外部キーの関係
外部キー制約を理解するには、主キーとの関係を知る必要があります。
主キー(PRIMARY KEY)
テーブル内のレコード(行)を一意に識別する列のことです。
外部キー(FOREIGN KEY)
別のテーブルの主キーを参照する列のことです。
| 用語 | 説明 | 例 |
|---|---|---|
| 親テーブル | 参照される側のテーブル | 顧客テーブル |
| 子テーブル | 参照する側のテーブル | 注文テーブル |
| 参照列 | 子テーブルの外部キー列 | 注文テーブルのcustomer_id |
| 被参照列 | 親テーブルの主キー列 | 顧客テーブルのid |
参照整合性とは
参照整合性(さんしょうせいごうせい)は、外部キー制約によって保たれるデータの一貫性のことです。
具体的には、以下のようなルールが守られます。
子テーブルに登録できる値
親テーブルに存在する値か、NULLのみ登録できます。
親テーブルのデータ削除
子テーブルから参照されているデータは、基本的に削除できません。
親テーブルのデータ更新
子テーブルから参照されている値を変更すると、設定によって子テーブルも連動して更新されます。
FOREIGN KEY制約が必要な理由
なぜ外部キー制約を設定する必要があるのでしょうか。
データの矛盾を防ぐ
外部キー制約がないと、こんな問題が起きます。
存在しないIDを参照できてしまう
顧客テーブルに存在しない顧客ID「999」を、注文テーブルに登録できてしまいます。
孤立したデータが生まれる
顧客を削除しても、その顧客の注文データが残り続けます。注文データを見ても、誰の注文か分からない状態になるんです。
データの信頼性が低下
こうした矛盾したデータが蓄積すると、正確な分析や集計ができなくなります。
データベースレベルで保護できる
アプリケーションのコードだけで整合性をチェックすることもできます。しかし、データベース自体に制約を設定しておけば、より確実です。
複数のアプリケーションからアクセスしても安全
Webアプリ、管理画面、バッチ処理など、どこからデータを操作しても、データベースが自動的に整合性を守ってくれます。
人為的ミスを防げる
SQLを直接実行する際も、データベースが間違いを防いでくれます。
保守性の向上
外部キー制約を設定しておくと、テーブル間の関連が明確になります。
新しく参加した開発者も、データベース構造を理解しやすくなるんです。
FOREIGN KEY制約の作成方法
それでは、実際に外部キー制約を設定する方法を見ていきましょう。
テーブル作成時に設定する
新しくテーブルを作るときに、同時に外部キー制約を定義できます。
例:顧客テーブルと注文テーブル
-- 親テーブル:顧客テーブル
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL
);
-- 子テーブル:注文テーブル
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
ポイント
FOREIGN KEY (customer_id):外部キーとなる列を指定REFERENCES customers(id):参照先のテーブルと列を指定- 親テーブルは先に作成しておく必要があります
制約に名前を付ける
外部キー制約には、分かりやすい名前を付けることができます。
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CONSTRAINTの後に、制約の名前(この例ではfk_orders_customer)を指定します。
名前を付けておくと、後で制約を削除したり変更したりするときに便利ですよ。
既存のテーブルに追加する
すでに存在するテーブルに、後から外部キー制約を追加することもできます。
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
ALTER TABLE文で、既存のテーブル構造を変更できるんです。
複数列での外部キー制約
複数の列を組み合わせた外部キー制約も設定できます。
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id, product_id)
REFERENCES order_products(order_id, product_id)
);
この例では、order_idとproduct_idの組み合わせで外部キーを設定しています。
ON DELETEとON UPDATEオプション
外部キー制約には、親テーブルのデータを変更したときの動作を指定できます。
ON DELETEオプション
親テーブルのレコードが削除されたとき、子テーブルのデータをどう扱うか設定します。
CASCADE(カスケード)
親レコードを削除すると、関連する子レコードも自動的に削除されます。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE
使用例
顧客を削除したら、その顧客の注文もすべて削除したい場合に使います。
RESTRICT(制限)
子レコードが存在する場合、親レコードの削除を拒否します。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE RESTRICT
使用例
注文が存在する顧客は削除できないようにしたい場合に使います。
SET NULL(ヌル設定)
親レコードを削除すると、子レコードの外部キー列がNULLになります。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE SET NULL
使用例
顧客を削除しても注文履歴は残したいが、顧客IDはNULLにしたい場合に使います。
SET DEFAULT(デフォルト設定)
親レコードを削除すると、子レコードの外部キー列がデフォルト値になります。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE SET DEFAULT
NO ACTION(何もしない)
RESTRICTと似ていますが、タイミングが異なります。通常はRESTRICTを使う方が安全です。
ON UPDATEオプション
親テーブルの主キーが更新されたとき、子テーブルのデータをどう扱うか設定します。
CASCADE
親レコードの主キーが変更されると、子レコードの外部キーも自動的に更新されます。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON UPDATE CASCADE
その他のオプション
ON DELETEと同じく、RESTRICT、SET NULL、SET DEFAULT、NO ACTIONが使えます。
オプションの組み合わせ
削除と更新で、異なるオプションを設定できます。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE
ON UPDATE CASCADE
この設定なら、親レコードの削除や更新が、子レコードにも自動的に反映されます。
FOREIGN KEY制約の確認方法

設定した外部キー制約を確認する方法を見ていきましょう。
MySQLでの確認方法
テーブル構造を表示
SHOW CREATE TABLE orders;
テーブルの作成文が表示され、外部キー制約の設定も確認できます。
外部キー制約の一覧を表示
SELECT
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = 'データベース名'
AND REFERENCED_TABLE_NAME IS NOT NULL;
データベース内のすべての外部キー制約が一覧表示されます。
PostgreSQLでの確認方法
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE
tc.constraint_type = 'FOREIGN KEY';
テーブルの依存関係を確認
どのテーブルがどのテーブルを参照しているか、全体像を把握することも重要です。
-- MySQLの場合
SELECT
TABLE_NAME AS 子テーブル,
COLUMN_NAME AS 外部キー列,
REFERENCED_TABLE_NAME AS 親テーブル,
REFERENCED_COLUMN_NAME AS 参照列
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = 'your_database'
AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY
TABLE_NAME;
FOREIGN KEY制約の削除・変更方法
外部キー制約を削除したり、設定を変更したりする方法です。
制約の削除
外部キー制約を削除するには、制約名が必要です。
MySQLの場合
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_customer;
PostgreSQLの場合
ALTER TABLE orders
DROP CONSTRAINT fk_orders_customer;
制約名が分からない場合は、先ほどの確認方法で調べましょう。
制約の変更
外部キー制約自体を直接変更することはできません。
設定を変えたい場合は、一度削除してから、新しい設定で作り直します。
-- 1. 既存の制約を削除
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_customer;
-- 2. 新しい設定で制約を作成
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE
ON UPDATE CASCADE;
外部キー制約のメリットとデメリット
外部キー制約には、メリットとデメリットがあります。
メリット
データの整合性が保たれる
不正なデータの登録を防ぎ、データの信頼性が向上します。
自動的なチェック
アプリケーションコードでチェック処理を書かなくても、データベースが自動的に検証してくれます。
ドキュメントとしての役割
テーブル間の関係が明確になり、データベース設計が理解しやすくなります。
CASCADE機能
関連データを一括で削除・更新できるので、処理がシンプルになります。
デメリット
パフォーマンスへの影響
制約のチェックに時間がかかり、大量データの挿入や更新が遅くなることがあります。
削除の制約
RESTRICTを設定していると、参照されているデータを削除できなくなります。
設計の複雑化
循環参照(テーブルAがBを参照し、BがAを参照)などがあると、設計が複雑になります。
マイグレーションの手間
既存のデータベースに外部キー制約を追加する際、既存データの整合性を取る必要があります。
よくあるエラーと対処法
FOREIGN KEY制約を使う際によく遭遇するエラーと、その解決方法です。
エラー1:制約違反で挿入できない
エラーメッセージ例
Cannot add or update a child row: a foreign key constraint fails
原因
子テーブルに登録しようとした値が、親テーブルに存在しません。
対処法
- 親テーブルに該当するレコードが存在するか確認
- 存在しない場合は、先に親テーブルにデータを登録
- 正しいIDを使って、子テーブルに登録
例
-- 間違い:顧客ID 999が存在しない
INSERT INTO orders (customer_id, order_date, total_amount)
VALUES (999, '2024-01-15', 10000);
-- エラーが発生
-- 正しい:存在する顧客IDを使う
INSERT INTO orders (customer_id, order_date, total_amount)
VALUES (1, '2024-01-15', 10000);
-- 成功
エラー2:親レコードを削除できない
エラーメッセージ例
Cannot delete or update a parent row: a foreign key constraint fails
原因
削除しようとしている親レコードが、子テーブルから参照されています。
対処法
- 先に子テーブルの関連レコードを削除する
- または、ON DELETE CASCADEを設定して、自動削除できるようにする
例
-- 間違い:注文が存在する顧客を削除しようとする
DELETE FROM customers WHERE id = 1;
-- エラーが発生
-- 対処法1:先に注文を削除
DELETE FROM orders WHERE customer_id = 1;
DELETE FROM customers WHERE id = 1;
-- 成功
-- 対処法2:CASCADE設定に変更(事前に制約を作り直す必要あり)
ALTER TABLE orders
DROP FOREIGN KEY fk_orders_customer;
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE;
-- これで顧客を削除すると、注文も自動削除される
DELETE FROM customers WHERE id = 1;
-- 成功
エラー3:データ型が一致しない
エラーメッセージ例
Referencing column and referenced column are incompatible
原因
外部キー列と参照先の列のデータ型が一致していません。
対処法
両方の列を同じデータ型にします。
例
-- 間違い:データ型が異なる
CREATE TABLE customers (
id BIGINT PRIMARY KEY -- BIGINTを使用
);
CREATE TABLE orders (
customer_id INT, -- INTを使用(型が違う)
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- エラーが発生
-- 正しい:同じデータ型にする
CREATE TABLE orders (
customer_id BIGINT, -- BIGINTに合わせる
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- 成功
エラー4:インデックスが存在しない
原因
一部のデータベースでは、外部キー列にインデックスが必要です。
対処法
外部キー列にインデックスを作成します。
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id);
ベストプラクティス
FOREIGN KEY制約を効果的に使うためのポイントをまとめました。
制約には必ず名前を付ける
制約名を付けておくと、後からの管理が楽になります。
命名規則の例
fk_{子テーブル名}_{親テーブル名}
fk_{子テーブル名}_{列名}
-- 良い例
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
-- 悪い例(名前なし)
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETEとON UPDATEを明示的に指定
デフォルトの動作に頼らず、明示的にオプションを指定しましょう。
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE RESTRICT
ON UPDATE CASCADE
これで、意図した動作が明確になります。
CASCADE使用時は慎重に
ON DELETE CASCADEは便利ですが、予期しないデータ削除につながる危険もあります。
本当に連動して削除してよいケースだけに使いましょう。
外部キー列にはインデックスを作成
外部キー列にインデックスを作成しておくと、結合(JOIN)のパフォーマンスが向上します。
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
MySQLなどでは自動的に作成されますが、明示的に作成しておくと確実です。
NOT NULL制約と組み合わせる
外部キー列にNULLを許可するかどうか、明確に決めましょう。
-- NULLを許可しない場合
customer_id INT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
-- NULLを許可する場合(オプショナルな関連)
manager_id INT NULL,
FOREIGN KEY (manager_id) REFERENCES employees(id)
循環参照を避ける
テーブルAがBを参照し、BがAを参照するような循環は避けましょう。
設計を見直して、中間テーブルを作るなどの工夫をします。
まとめ
FOREIGN KEY制約(外部キー制約)について解説してきました。
重要なポイント
- 外部キー制約は、テーブル間の関連性を定義してデータの整合性を保つ仕組み
- 親テーブルと子テーブルの関係を設定し、参照整合性を維持できる
- CREATE TABLEまたはALTER TABLEで制約を設定する
- ON DELETE、ON UPDATEオプションで、親データ変更時の動作を制御できる
- CASCADEは便利だが、使用時は慎重な判断が必要
- 制約には分かりやすい名前を付けると管理しやすい
- データ型の一致、インデックスの作成など、設定時の注意点がある
外部キー制約を適切に設定すれば、データベースの信頼性が大きく向上します。
最初は制約のエラーに戸惑うかもしれませんが、それはデータベースが不正なデータを防いでくれている証拠です。
まずは簡単な2つのテーブルの関係から始めて、徐々に複雑な設計にも挑戦してみてください。安全で整合性の高いデータベースを構築しましょう!

コメント