LoginSignup
0
0

More than 5 years have passed since last update.

generated column + 外部キーをいろいろ試してみた

Posted at

generated column + 外部キーをいろいろ試してみた

※ mysql 5.7.10 で試しています

準備

実験用テーブルを用意します。

DROP TABLE IF EXISTS table3;
DROP TABLE IF EXISTS table2;
DROP TABLE IF EXISTS table1;

CREATE TABLE table1 (
  id INT(11) NOT NULL AUTO_INCREMENT,
  fkval1 INT(11) NULL DEFAULT NULL,
  PRIMARY KEY (id),
  INDEX idx_table1 (fkval1)
);

CREATE TABLE table2 (
  id INT(11) NOT NULL AUTO_INCREMENT,
  val INT(11),
  fkval2 INT(11) AS (val * 2) STORED,
  PRIMARY KEY (id),
  INDEX idx_table2 (fkval2),
  CONSTRAINT fk1 FOREIGN KEY (fkval2) REFERENCES table1 (fkval1)
);

CREATE TABLE table3 (
  id INT(11) NOT NULL AUTO_INCREMENT,
  fkval3 INT(11) NULL DEFAULT NULL,
  PRIMARY KEY (id),
  CONSTRAINT fk2 FOREIGN KEY (fkval3) REFERENCES table2 (fkval2) ON UPDATE CASCADE ON DELETE CASCADE
);

table1.fkval1 -> table2.fkval2 -> table3.fkval3 という親子関係です。
table2.fkval2val を元にした generated column です(2倍の値)。
id はただの癖なので特に意味はありません。

実験(成功)

これらのテーブルにレコードを挿入してみます。

INSERT INTO table1 (fkval1) VALUES (10);
INSERT INTO table2 (val) VALUES (5);
INSERT INTO table3 (fkval3) VALUES (10);

特にエラーは出ません。

table1 に制約は存在しないため、fkval1 はなんでも入ります。ここでは 10 を入れています。

table2 は table1 への外部キー制約が存在するため、fkval2 の値は ↑で入れた 10 の値でなければなりません。
エラーはあとで試すのでとりあえず入るであろう 5 を入れています。

table3 は table2 への外部キー制約が存在するため、fkval3 の値は ↑で入れた 10 の値でなければなりません。
エラーはあとで試すのでとりあえず入るであろう 10 を入れています。

実験(失敗)

次に外部キー制約に引っかかるようなレコードを入れてみます。

INSERT INTO table2 (val) VALUES (10);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`table2`, CONSTRAINT `fk1` FOREIGN KEY (`fkval2`) REFERENCES `table1` (`fkval1`))

怒られました。
table2.val に 10 を突っ込むと fkval2 が20になり、外部キーに違反するからです。
これにより、 generated column を外部キーの参照側にしても正常に動作していることがわかります。

INSERT INTO table3 (fkval3) VALUES (20);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`table3`, CONSTRAINT `fk2` FOREIGN KEY (`fkval3`) REFERENCES `table2` (`fkval2`))

怒られました。
table2.fkval2 に 20 なんてレコードはないため、 table3.fkval3 に 20 を突っ込もうとして外部キー違反になるからです。
これにより、 generated column を外部キーの被参照側にしても正常に動作していることがわかります。

逆に言えば INSERT INTO table1 (fkval1) VALUES (20) を実行した後は上の2クエリは成功することになります。
実験(成功)と意味的には同じですが一応試しておきます。

INSERT INTO table1 (fkval1) VALUES (20);
Query OK, 1 row affected (0.00 sec)
INSERT INTO table2 (val) VALUES (10);
Query OK, 1 row affected (0.00 sec)
INSERT INTO table3 (fkval3) VALUES (20);
Query OK, 1 row affected (0.00 sec)

OK のようです。

参照アクション

ON UPDATE, ON DELETE を省略(RESTRICT)してるので他の参照アクションを試してみます。

ALTER TABLE table2 DROP FOREIGN KEY fk1;
ALTER TABLE table2 ADD CONSTRAINT fk1 FOREIGN KEY (fkval2) REFERENCES table1 (fkval1) ON UPDATE CASCADE ON DELETE CASCADE;
ERROR 3104 (HY000): Cannot define foreign key with ON UPDATE CASCADE clause on a generated column.

怒られました。CASCADE にはできないようです。

http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html に記載がありますが、確かに試してみたところ、RESTRICT か NOT ACTION しか設定できないようです。
ただ「ON UPDATE CASCADE はダメだよ!」とも言っているので、 ON DELETE だけを試したところ普通に行けるようでした。

ALTER TABLE table2 ADD CONSTRAINT fk1 FOREIGN KEY (`fkval2`) REFERENCES `table1` (`fkval1`) ON UPDATE RESTRICT ON DELETE CASCADE;
Query OK, 2 rows affected (0.10 sec)
Records: 2  Duplicates: 0  Warnings: 0

generated column を外部キーの参照側にすることはめったに無いと思うし、個人的には ON UPDATE は割りとどうでもよくて ON DELETE の方が大事だと思うのでこれでも十分嬉しかったりします。
一応効いているか試してみます。

SELECT
  (SELECT COUNT(*) FROM table1) as table1count,
  (SELECT COUNT(*) FROM table2) as table2count,
  (SELECT COUNT(*) FROM table3) as table3count;
+-------------+-------------+-------------+
| table1count | table2count | table3count |
+-------------+-------------+-------------+
|           2 |           2 |           2 |
+-------------+-------------+-------------+

DELETE FROM table1;

SELECT
  (SELECT COUNT(*) FROM table1) as table1count,
  (SELECT COUNT(*) FROM table2) as table2count,
  (SELECT COUNT(*) FROM table3) as table3count;
+-------------+-------------+-------------+
| table1count | table2count | table3count |
+-------------+-------------+-------------+
|           0 |           0 |           0 |
+-------------+-------------+-------------+

最上位の table1 を吹き飛ばすと全て吹き飛んでいるのがわかります。

結論というか感想

「(個人的には) generated column はほぼ普通のカラムと同じように外部キー制約を活用できる」

都道府県コードと市区町村コードのように、「親子関係があり、かつ子キー値が親キー値を内包している」ときにいい感じに出来るかもしれません。
例えば東京都コードは「13」で、千代田区コード「13101」ですが、これのテーブル定義、いつも悩むんですよ。。。

  • t_pref
    • pref_cd: 13
    • pref_name: 東京都
  • t_city
    • city_cd: 13101
    • city_name: 千代田区

という構造だと外部キー制約が貼れないのでつらみがある。

  • t_pref
    • pref_cd: 13
    • pref_name: 東京都
  • t_city
    • pref_cd: 13
    • city_cd: 101
    • city_name: 千代田区

だと外部キー制約は貼れるけど、13101 という値を得るのに一手間必要だし、t_city を参照したいトランザクションテーブルで外部キーのために2カラム必要になる。

  • t_pref
    • pref_cd: 13
    • pref_name: 東京都
  • t_city
    • pref_cd: 13
    • city_cd: 13101
    • city_name: 千代田区

だと外部キーは貼れるし、13101 という値も直に得られるけど、13 を冗長に持ってることになる。

そこで2番目の方法で CONCAT(pref_cd, city_cd) (必要に応じて LPAD も)で generated column すればいい。
city_cd が必要なテーブルではその generated colum に対して外部キーを貼れば単一カラムで済む。

まぁ city_cd を持つようなテーブルは得てして pref_cd も持つので意味ないんだけどね…。まぁ例ということで。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0