Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

MySQL 外部キー制約の種類と特徴

More than 1 year has passed since last update.

DB設計を行うとテーブルの依存関係から外部キー制約の検討もあると思います。
そこで、制約の種類と特徴を確認しておきましょう。

外部キー制約の種類

MySQLの外部キー制約は以下の種類があります。
・RESTRICT
・CASCADE
・SET NULL
・NO ACTION
どんな違いがあるのか確認してみましょう。

確認方法として

親テーブルとなる基礎データの作成を行い、各外部制約キーとなる子テーブルを作成し、テーブルの更新・削除にて確認を行う。

-- 親テーブルの作成
create table area(
id int, 
name varchar(10), 
index(name)
) engine=InnoDB;

※ 親テーブルには県コード一覧を登録

mysql> select * from area limit 10;
+------+--------+
| id   | name   |
+------+--------+
|    1 | 北海道 |
|    2 | 青森県 |
|    3 | 岩手県 |
|    4 | 宮城県 |
|    5 | 秋田県 |
|    6 | 山形県 |
|    7 | 福島県 |
|    8 | 茨城県 |
|    9 | 栃木県 |
|   10 | 群馬県 |
+------+--------+
10 rows in set (0.06 sec)

RESTRICT

ON DELETE句やON UPDATE句にRESTRICTを指定した場合は、親テーブルに対して更新や削除を行うとエラーが発生します。

-- 子テーブルの作成(RESTRICT)
create table case_restrict(
id int, 
name varchar(10), 
in_date date, 
index(name),
foreign key(name) references area(name) on update restrict
) engine=InnoDB;

登録内容を確認

mysql> select * from area limit 10;
+------+--------+
| id   | name   |
+------+--------+
|    1 | 北海道 |
|    2 | 青森県 |
|    3 | 岩手県 |
|    4 | 宮城県 |
|    5 | 秋田県 |
|    6 | 山形県 |
|    7 | 福島県 |
|    8 | 茨城県 |
|    9 | 栃木県 |
|   10 | 群馬県 |
+------+--------+
10 rows in set (0.00 sec)

mysql> select * from case_restrict;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | 北海道 | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | 北海道 | 2018-12-31 |
|    8 | 北海道 | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

areaを変更してみる

mysql> update area set name="北海道変更" where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`db1`.`case_restrict`, CONSTRAINT `case_restrict_ibfk_1` FOREIGN KEY (`name`) REFERENCES `area` (`name`))

変更を行うことができませんでした。

areaから削除してみる

mysql> delete from area where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`db1`.`case_restrict`, CONSTRAINT `case_restrict_ibfk_1` FOREIGN KEY (`name`) REFERENCES `area` (`name`))

更新時と同様にエラーとなりました。

CASCADE

ON DELETE句やON UPDATE句にCASCADEを指定した場合は、親テーブルに対して更新を行うと子テーブルで同じ値を持つカラムの値も合わせて更新されます。

-- 子テーブルの作成(CASCADE)
create table case_cascade(
id int, 
name varchar(10), 
in_date date, 
index(name),
foreign key(name) references area(name) on update cascade on delete cascade
) engine=InnoDB;

登録内容を確認

mysql> select * from area limit 10;
+------+--------+
| id   | name   |
+------+--------+
|    1 | 北海道 |
|    2 | 青森県 |
|    3 | 岩手県 |
|    4 | 宮城県 |
|    5 | 秋田県 |
|    6 | 山形県 |
|    7 | 福島県 |
|    8 | 茨城県 |
|    9 | 栃木県 |
|   10 | 群馬県 |
+------+--------+
10 rows in set (0.00 sec)

mysql> select * from case_cascade;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | 北海道 | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | 北海道 | 2018-12-31 |
|    8 | 北海道 | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

areaを変更してみる

mysql> update area set name='北海道変更' where id=1;
Query OK, 1 row affected (0.06 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from case_cascade;
+------+------------+------------+
| id   | name       | in_date    |
+------+------------+------------+
|    1 | 北海道変更 | 2018-12-31 |
|    2 | 青森県     | 2018-12-31 |
|    3 | 岩手県     | 2018-12-31 |
|    4 | 宮城県     | 2018-12-31 |
|    5 | 秋田県     | 2018-12-31 |
|    6 | 山形県     | 2018-12-31 |
|    7 | 北海道変更 | 2018-12-31 |
|    8 | 北海道変更 | 2018-12-31 |
|    9 | 青森県     | 2018-12-31 |
+------+------------+------------+
9 rows in set (0.00 sec)

areaの変更からcase_cascadeの内容も同時に変更されました。

areaから削除してみる

mysql> delete from area where id=1;
Query OK, 1 row affected (0.12 sec)

mysql> select * from case_cascade;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
6 rows in set (0.00 sec)

areaを削除同時に対象となるcase_cascadeも同時に削除されました

SET NULL

ON DELETE句やON UPDATE句にSET NULLを指定した場合は、親テーブルに対して更新を行うと子テーブルで同じ値を持つカラムの値がNULLに更新されます。

-- 子テーブルの作成(SET NULL)
create table case_set_null(
id int, 
name varchar(10), 
in_date date, 
index(name),
foreign key(name) references area(name) on update set null on delete set null
) engine=InnoDB;

登録内容を確認

mysql> select * from area limit 10;
+------+--------+
| id   | name   |
+------+--------+
|    2 | 青森県 |
|    3 | 岩手県 |
|    4 | 宮城県 |
|    5 | 秋田県 |
|    6 | 山形県 |
|    7 | 福島県 |
|    8 | 茨城県 |
|    9 | 栃木県 |
|   10 | 群馬県 |
|   11 | 埼玉県 |
+------+--------+
10 rows in set (0.00 sec)

mysql> select * from case_set_null;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | 北海道 | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | 北海道 | 2018-12-31 |
|    8 | 北海道 | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

areaを変更してみる

mysql> update area set name="北海道変更" where id=1;
Query OK, 1 row affected (0.09 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from case_set_null;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | NULL   | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | NULL   | 2018-12-31 |
|    8 | NULL   | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

変更を行った結果、case_set_nullで登録していた「北海道」はNULLに変更されました

areaから削除してみる

mysql> delete from area where id=1;
Query OK, 1 row affected (0.12 sec)

mysql>  select * from case_set_null;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | NULL   | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | NULL   | 2018-12-31 |
|    8 | NULL   | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

変更を行った結果、case_set_nullで登録していた「北海道」はNULLに変更されました。
この結果はareaを変更した場合と同じですね。

NO ACTION

ON DELETE句やON UPDATE句にNO ACTIONを指定した場合は、親テーブルに対して更新や削除を行うとエラーが発生します。

-- 子テーブルの作成(NO ACTION)
create table case_no_action(
id int, 
name varchar(10), 
in_date date, 
index(name),
foreign key(name) references area(name) on delete no action
) engine=InnoDB;

登録内容を確認

mysql> select * from area limit 10;
+------+--------+
| id   | name   |
+------+--------+
|    2 | 青森県 |
|    3 | 岩手県 |
|    4 | 宮城県 |
|    5 | 秋田県 |
|    6 | 山形県 |
|    7 | 福島県 |
|    8 | 茨城県 |
|    9 | 栃木県 |
|   10 | 群馬県 |
|   11 | 埼玉県 |
+------+--------+
10 rows in set (0.00 sec)

mysql> select * from case_no_action;
+------+--------+------------+
| id   | name   | in_date    |
+------+--------+------------+
|    1 | 北海道 | 2018-12-31 |
|    2 | 青森県 | 2018-12-31 |
|    3 | 岩手県 | 2018-12-31 |
|    4 | 宮城県 | 2018-12-31 |
|    5 | 秋田県 | 2018-12-31 |
|    6 | 山形県 | 2018-12-31 |
|    7 | 北海道 | 2018-12-31 |
|    8 | 北海道 | 2018-12-31 |
|    9 | 青森県 | 2018-12-31 |
+------+--------+------------+
9 rows in set (0.00 sec)

areaを変更してみる

mysql> update area set name="北海道変更" where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`db1`.`case_no_action`, CONSTRAINT `case_no_action_ibfk_1` FOREIGN KEY (`name`) REFERENCES `area` (`name`) ON DELETE NO ACTION)

変更ができませんでした。

areaから削除してみる

mysql> delete from area where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`db1`.`case_no_action`, CONSTRAINT `case_no_action_ibfk_1` FOREIGN KEY (`name`) REFERENCES `area` (`name`) ON DELETE NO ACTION)

削除ができませんでした。

結果として

上記の確認結果からこのように結果がまとまりました。

外部キー制約 親テーブル更新 親テーブル削除
RESTRICT エラーになり更新できない エラーになり削除できない
CASCADE 親テーブルの変更にともない、子テーブルも変更に追従する 親テーブルの削除にともない、参照先が無くなるので子テーブルも同時に削除される
SET NULL 親テーブルの変更にともない、子テーブルの参照先がなくなるのでNULLに置き換わる 親テーブルの変更にともない、子テーブルの参照先がなくなるのでNULLに置き換わる
NO ACTION エラーになり更新できない エラーになり削除できない

テーブルの依存性や、親テーブルとなるデータの内容から勘案して、テーブル定義として検討してみるのもいいですね

masapiko
メモ書きしていきます →しばらく休止します
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away