ON DELETE/UPDATE ポリシー
ON DELETE/UPDATE ポリシー とは
SQLにおける外部キー制約、ON DELETE/UPDATEポリシーについてSQLで実践的に設定・確認します。
なぜ重要か
外部キーは「子から見て親が必ず存在する」ことを保証しますが、親が削除されたり親のキー値が更新されたりしたとき、子テーブルをどう扱うかは複数の選択肢があります。この挙動を制御するのが ON DELETE と ON UPDATE のポリシーです。設定を間違えると、ECサイトで顧客を退会させた瞬間に過去の注文が全部消える、といった事故が起きます。実務でテーブル設計をする以上、これを意識しないわけにはいきません。
5 つのポリシー
PostgreSQL がサポートする ON DELETE / ON UPDATE のアクションは次の 5 つです。
NO ACTION(デフォルト): 参照されている親レコードの削除・更新を拒否RESTRICT: NO ACTION とほぼ同義。参照中なら拒否CASCADE: 親の削除・更新を子にも自動波及SET NULL: 子の外部キーを NULL に書き換える (FK 列が NULL 許可必須)SET DEFAULT: 子の外部キーを DEFAULT 値に書き換える
構文の場所
SQL クエリ
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT REFERENCES customers(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);REFERENCES 親(列) の直後に ON DELETE <action> と ON UPDATE <action> を続けます。両方指定可能で、それぞれ独立に動きます。テーブルレベル記法でも書き方は同じです。
SQL クエリ
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);どう選ぶか (典型的な使い分け)
- 「親の子は自分の家族のような関係」 (例 注文と注文明細) -> CASCADE
- 「親を消すと履歴が消える」 (例 顧客と注文) -> RESTRICT。論理削除や別テーブル退避を検討
- 「親は消えたが子だけ独立して残したい」 (例 担当営業が退職、案件は残る) -> SET NULL
動作確認の例
SQL クエリ
-- ON DELETE CASCADE: 顧客を消すと注文も消える
DELETE FROM customers WHERE id = 1;
-- -> 田中の注文がすべて消えるON UPDATE の使いどころ
親の主キーが更新されるケースは比較的レアですが、自然キー (例 user.email) を外部キーにしている場合に重宝します。ON UPDATE CASCADE を付けておけば、メールアドレス変更時に子テーブルの参照値も自動で書き換わります。
ポリシーを書かないと暗黙的に
NO ACTION(= 参照中なら拒否)。明示的に書いておくとレビュー時に意図が伝わるので CASCADE などを採用する場合は省略しない。
CASCADE は強力ですが「気付かないうちに大量データが消える」事故の温床。実務では論理削除フラグ (例 deleted_at) を併用するパターンもよく使われます。
RESTRICT と NO ACTION の違い
どちらも「参照中なら親の操作を拒否」しますが、厳密にはタイミングが違います。
RESTRICT: 親への操作が来た瞬間にチェック。トリガーや遅延制約より先に弾くNO ACTION: SQL 文の終了時にチェック。同じトランザクション内で先に子を消せばOK
単純な業務 DDL ではどちらでも同じ結果になることが多いので、最初は「拒否」とだけ覚えておけば実用上は問題ありません。複雑なトランザクション処理を組むときに思い出せれば十分です。
SET DEFAULT の落とし穴
SET DEFAULT は「子の FK を DEFAULT 値に書き換える」ポリシーで、たとえば assignee_id INT DEFAULT 0 のような既定値を持たせるパターンと組み合わせます。便利そうに見えますが、その DEFAULT 値が親テーブルに実在しないと別の参照整合性エラーになります。SET DEFAULT を使う場合は「DEFAULT 値 = ダミーの親レコードを必ず用意する」というセットで運用するのが鉄則です。
まとめ
- ポリシーは NO ACTION / RESTRICT / CASCADE / SET NULL / SET DEFAULT の 5 種
- 何も指定しなければ NO ACTION (拒否) になる
- ON DELETE と ON UPDATE は別々に指定でき、両方書ける
- 関係の意味 (家族関係 / 履歴 / 独立) を考えて選ぶことが大事
- RESTRICT と NO ACTION はチェックタイミングの違いだけで結果はほぼ同じ
- SET DEFAULT を使うなら DEFAULT 値の親レコードを用意する
次のレッスン
次は CASCADE vs RESTRICT vs SET NULL です。外部キー制約におけるCASCADE、RESTRICT、SET NULLの違いを解説します。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- ON DELETE/UPDATE の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. ON DELETE/UPDATE とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.sql
CREATE TABLE customers (
id INT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
total INT NOT NULL,
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE
);
INSERT INTO customers (id, name) VALUES
(1, '田中'),
(2, '佐藤'),
(3, '鈴木');
INSERT INTO orders (id, customer_id, total) VALUES
(101, 1, 3000),
(102, 1, 1500),
(103, 2, 5000),
(104, 3, 2000);
期待される出力
| id | customer_id | total |
|---|---|---|
| 103 | 2 | 5000 |
| 104 | 3 | 2000 |