ON DELETE/UPDATE ポリシー

ON DELETE/UPDATE ポリシー とは

SQLにおける外部キー制約、ON DELETE/UPDATEポリシーについてSQLで実践的に設定・確認します。

なぜ重要か

外部キーは「子から見て親が必ず存在する」ことを保証しますが、親が削除されたり親のキー値が更新されたりしたとき、子テーブルをどう扱うかは複数の選択肢があります。この挙動を制御するのが ON DELETEON 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 );

どう選ぶか (典型的な使い分け)

diagram (will load when visible)
  • 「親の子は自分の家族のような関係」 (例 注文と注文明細) -> 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 つができることを確認しましょう。

  1. ON DELETE/UPDATE の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (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);

期待される出力

idcustomer_idtotal
10325000
10432000

ヒント

query.sql
query.sql
学習モード
コードの実行結果
データベースを初期化中...