Grow up

生活とプログラミング

外部キー制約について

はじめに

今回は「外部キー制約」の内容を勉強したのでまとめます。
Postgre SQL 10.4 を使用しています。

外部キー制約

テーブル間でデータを維持するための制約です。

構文

 FOREIGN KEY (カラム名[, …])
 REFERENCE 参照するテーブル [(参照するカラム [, …])]
 ※[ ]内は省略可能

外部キーで制約で参照される側のカラムには主キー制約(UNIQUE)もしくは一意制約(PRIMARY KEY)が設定されている必要があります。

デフォルトでの動作

参照テーブル  → 外部キーを参照する側のテーブル
被参照テーブル → 外部キーとして参照される側のテーブル

①被参照テーブルに存在しない値を持つ行を参照テーブルには挿入できない
②参照テーブルでは、被参照テーブルに存在しない値を持つ行の更新はできない
③被参照テーブルの行が参照テーブルから参照されている場合、被参照テーブルのその行は更新、削除できない

動作確認

外部キー制約のデフォルトでの動作を確認してみます。

まずは被参照テーブルとなる商品テーブル products を作成します。

CREATE TABLE products (
product_id INTEGER PRIMARY KEY,
product_name TEXT NOT NULL,
product_price INTEGER NOT NULL
 );

商品となる カメラ を追加しておきます。

INSERT INTO products(product_id, product_name, product_price) VALUES(1, 'camera', 30000);


次に参照テーブルとなる売上テーブル sales を作成します。
sales の product_id は products の product_id を外部キーとします。

CREATE TABLE sales (
sales_id INTEGER PRIMARY KEY,
product_id INTEGER REFERENCES products(product_id)
);

カメラを売り上げたとして salses の product_id に 1 を追加しておきます。

INSERT INTO sales(sales_id, product_id) VALUES(1,1);


①被参照テーブルに存在しない値を持つ行を参照テーブルには挿入できない
 products に存在しない product_id が 2 となる商品を sales へ追加するとエラーになります。

test=>  INSERT INTO sales(sales_id, product_id) VALUES(2,2);
ERROR:  insert or update on table "sales" violates foreign key constraint "sales_product_id_fkey"
DETAIL:  Key (product_id)=(2) is not present in table "products".


②参照テーブルでは、被参照テーブルに存在しない値を持つ行の更新はできない
 products に存在しない商品である 2 に sales の product_id を更新するとエラーになります。

UPDATE sales SET product_id = 2 WHERE sales_id = 1;
ERROR:  insert or update on table "sales" violates foreign key constraint "sales_product_id_fkey"
DETAIL:  Key (product_id)=(2) is not present in table "products".


③被参照テーブルの行が参照テーブルから参照されている場合、被参照テーブルのその行は更新、削除できない
 sales から参照されている products の product_id を 1 から 2 に更新するとエラーになります。

UPDATE products SET product_id = 2 WHERE product_id = 1;
ERROR:  update or delete on table "products" violates foreign key constraint "sales_product_id_fkey" on table "sales"
DETAIL:  Key (product_id)=(1) is still referenced from table "sales".

 sales から参照されている products の product_id が 1 のレコードを削除するとエラーになります 。

DELETE FROM  products  WHERE product_id = 1;
ERROR:  update or delete on table "products" violates foreign key constraint "sales_product_id_fkey" on table "sales"
DETAIL:  Key (product_id)=(1) is still referenced from table "sales".


 sales の product_id が 1 のレコードをすべて削除すると products の product_id を削除または更新することが出来ます。

CASCADE オプション

 CASCADEオプションを使用することで、テーブル間の整合性を効率的に保てます。

①ON DELETE CASCADE
 被参照テーブルの行を削除すると、参照テーブルの該当行も同時に削除されます。

②ON UPDATE CASCADE
 被参照テーブルの行を削除すると、参照テーブルの該当行も同時に削除されます。

先ほど使用していた sales に CASCADE オプションを追加してみます。

外部キー制約の更新を行うSQL文は Postgre SQL 10.4 でも見つからなかったため以下の手順を踏みます。

1.既存の制約を削除
2.改めて制約を追加

▼ tratail に同様の質問がありました
teratail.com

1.既存の制約を削除

ALTER TABLE COMMENT DROP CONSTRAINT sales_product_id_fkey;

削除する際に制約の名前を確認したい場合は \d [テーブル名] で確認できます。

\d sales
                   テーブル "public.sales"
     列     |   型    | 照合順序 | Null 値を許容 | デフォルト
------------+---------+----------+---------------+------------
 sales_id   | integer |          | not null      |
 product_id | integer |          |               |
インデックス:
    "sales_pkey" PRIMARY KEY, btree (sales_id)
外部キー制約:
    "sales_product_id_fkey" FOREIGN KEY (product_id) REFERENCES products(product_id)


2.改めて制約を追加

 ALTER TABLE sales ADD FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE ON UPDATE CASCADE;


CASCADEオプションを追加した時の動作を確認してみます。

products の product_id を 1 から 2 に更新を行うと、sales の product_id も同様に更新されます。

postgresql=> SELECT * FROM sales;
 sales_id | product_id
----------+------------
        1 |          1
(1 行)

postgresql=> SELECT * FROM products;
 product_id | product_name | product_price
------------+--------------+---------------
          1 | camera       |         30000
(1 行)

UPDATE products SET product_id = 2 WHERE product_id = 1;
UPDATE 1

postgresql=> SELECT * FROM sales;
 sales_id | product_id
----------+------------
        1 |          2
(1 行)

postgresql=> SELECT * FROM products;
 product_id | product_name | product_price
------------+--------------+---------------
          2 | camera       |         30000
(1 行)


products の product_id が 2 のレコードを削除すると、sales の product_id も同様に削除されます。

postgresql=> SELECT * FROM sales;
 sales_id | product_id
----------+------------
        1 |          2
(1 行)

postgresql=> SELECT * FROM products;
 product_id | product_name | product_price
------------+--------------+---------------
          2 | camera       |         30000
(1 行)

salesproduct=> DELETE FROM products WHERE product_id = 2;
DELETE 1

salesproduct=> select * from products;
 product_id | product_name | product_price
------------+--------------+---------------
(0 行)

salesproduct=> select * from sales;
 sales_id | product_id
----------+------------
(0 行)

.

テーブルの削除

被参照テーブルを削除する場合にはCASCADEオプションが必要になります。

CASCADEオプションを付けずに sales を削除するとエラーになります。

postgresql=> DROP TABLE products;
ERROR:  cannot drop table products because other objects depend on it
DETAIL:  constraint sales_product_id_fkey1 on table sales depends on table products
HINT:  Use DROP ... CASCADE to drop the dependent objects too.


CASCADEオプションを付けると products を削除することができます。

postgresql=> DROP TABLE products CASCADE ;
NOTICE:  drop cascades to constraint sales_product_id_fkey on table sales
DROP TABLE

postgresql=> \d
           リレーション一覧
 スキーマ | 名前  |    型    | 所有者
----------+-------+----------+--------
 public   | sales | テーブル | postgresql
(1 行)

postgresql=> select * from sales;
 sales_id | product_id
----------+------------
        1 |          1
(1 行)

postgresq=> \d sales
                   テーブル "public.sales"
     列     |   型    | 照合順序 | Null 値を許容 | デフォルト
------------+---------+----------+---------------+------------
 sales_id   | integer |          | not null      |
 product_id | integer |          |               |
インデックス:
    "sales_pkey" PRIMARY KEY, btree (sales_id)


products の product_id に ON DELETE CASCADE オプションを付けていても、テーブルを削除した場合には sales に product_id が残ります。
これは sales の外部キー制約のみが削除されるためです。

外部キー制約によって参照されている場合は、外部キー制約のみが削除され、その外部キーを持つテーブルそのものは削除されません。
引用元 : Postgre SQL 10.4 文書 SQLコマンド DROP TABLE
https://www.postgresql.jp/document/10/html/sql-droptable.html