generated columnそのものの説明はこのへんに。
日々の覚書: MySQL 5.7.6のgenerated columnは関数インデックスの夢を見るか
というわけでやってみましょう。
こんなテーブルがあったとするじゃろ?
valの中身をdatetimeにキャストする関数を食わせたgenerated columnを作るじゃろ?
なんと、日付として不正な値を入れると、キャストに失敗してエラーが返るんじゃよ!
もちろん、sql_modeがSTRICT_TRANS_TABLESかSTRICT_ALL_TABLESでないと、キャストの失敗を丸めちゃうので台無しになる。まさかもう非strict_modeとか選ばない…よね?
datetime型の場合はNOT NULL制約をつけても0000-00-00 00:00:00で丸めちゃうからアレだけど、IFをかませてtinyintとかにすると
おおおこれ結構良くない? このIF関数で無理矢理NULLを返す方式だと、NOT NULL制約の問題なので非strict_modeでもちゃんとエラーになってくれる。わーい(死)
ちなみに、既に格納されている値に対して後からこの似非CHECK制約を追加する場合、
generated columnの種類を指定しない場合、暗黙のデフォルトとしてVIRTUALになるので、
「VIRTUALなgenerated columnの場合は、今までのものは無視されて、新たに更新するものに対して有効」(VIRTUALなgenerated columnはALTER TABLEの時には何もせず、SELECTされた時に都度関数が走るから)になり、
「STOREDなgenerated columnの場合は、今までのもので似非CHECK制約を通らない行がある場合、そもそもALTER TABLEが通らない」(STOREDなgenerated columnはALTER TABLEの時に関数を通って計算され、それがデータに格納されるから)になる。
おおお面白いんじゃないこれ?
PlanetMySQL Voting: Vote UP / Vote DOWN
日々の覚書: MySQL 5.7.6のgenerated columnは関数インデックスの夢を見るか
というわけでやってみましょう。
mysql57> CREATE TABLE t1 (num int primary key, val varchar(32)) Engine= InnoDB;
Query OK, 0 rows affected (0.03 sec)
mysql57> INSERT INTO t1 (num, val) VALUES (1, '2015-08-06');
Query OK, 1 row affected (0.01 sec)
mysql57> SELECT * FROM t1;
+-----+------------+
| num | val |
+-----+------------+
| 1 | 2015-08-06 |
+-----+------------+
1 row in set (0.00 sec)
こんなテーブルがあったとするじゃろ?
mysql57> ALTER TABLE t1 ADD check_val datetime AS (CAST(val AS datetime));
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
valの中身をdatetimeにキャストする関数を食わせたgenerated columnを作るじゃろ?
mysql57> INSERT INTO t1 (num, val) VALUES (2, '2015-09-31');
ERROR 1292 (22007): Incorrect datetime value: '2015-09-31'
mysql57> SELECT * FROM t1;
+-----+------------+---------------------+
| num | val | check_val |
+-----+------------+---------------------+
| 1 | 2015-08-06 | 2015-08-06 00:00:00 |
+-----+------------+---------------------+
1 row in set (0.00 sec)
mysql57> INSERT INTO t1 (num, val) VALUES (2, '2015-09-30');
Query OK, 1 row affected (0.00 sec)
mysql57> SELECT * FROM t1;
+-----+------------+---------------------+
| num | val | check_val |
+-----+------------+---------------------+
| 1 | 2015-08-06 | 2015-08-06 00:00:00 |
| 2 | 2015-09-30 | 2015-09-30 00:00:00 |
+-----+------------+---------------------+
2 rows in set (0.00 sec)
なんと、日付として不正な値を入れると、キャストに失敗してエラーが返るんじゃよ!
mysql57> SET sql_mode= '';
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql57> SELECT @@sql_mode;
+------------+
| @@sql_mode |
+------------+
| |
+------------+
1 row in set (0.00 sec)
mysql57> INSERT INTO t1 (num, val) VALUES (3, '2015-11-31');
Query OK, 1 row affected, 1 warning (0.02 sec)
mysql57> SHOW WARNINGS;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1292 | Incorrect datetime value: '2015-11-31' |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
mysql57> SELECT * FROM t1;
+-----+------------+---------------------+
| num | val | check_val |
+-----+------------+---------------------+
| 1 | 2015-08-06 | 2015-08-06 00:00:00 |
| 2 | 2015-09-30 | 2015-09-30 00:00:00 |
| 3 | 2015-11-31 | NULL |
+-----+------------+---------------------+
3 rows in set, 1 warning (0.00 sec)
もちろん、sql_modeがSTRICT_TRANS_TABLESかSTRICT_ALL_TABLESでないと、キャストの失敗を丸めちゃうので台無しになる。まさかもう非strict_modeとか選ばない…よね?
datetime型の場合はNOT NULL制約をつけても0000-00-00 00:00:00で丸めちゃうからアレだけど、IFをかませてtinyintとかにすると
mysql57> CREATE TABLE t2 (num int primary key, val varchar(32), check_val tinyint AS (IF(val IN ('東京', '大阪'), 1, NULL)) NOT NULL) Engine= InnoDB;
Query OK, 0 rows affected (0.02 sec)
mysql57> INSERT INTO t2 (num, val) VALUES (1, '東京');
Query OK, 1 row affected (0.00 sec)
mysql57> INSERT INTO t2 (num, val) VALUES (2, '大阪');
Query OK, 1 row affected (0.01 sec)
mysql57> INSERT INTO t2 (num, val) VALUES (3, '奈良');
ERROR 1048 (23000): Column 'check_val' cannot be null
mysql57> SELECT * FROM t2;
+-----+--------+-----------+
| num | val | check_val |
+-----+--------+-----------+
| 1 | 東京 | 1 |
| 2 | 大阪 | 1 |
+-----+--------+-----------+
2 rows in set (0.00 sec)
おおおこれ結構良くない? このIF関数で無理矢理NULLを返す方式だと、NOT NULL制約の問題なので非strict_modeでもちゃんとエラーになってくれる。わーい(死)
ちなみに、既に格納されている値に対して後からこの似非CHECK制約を追加する場合、
mysql57> SELECT * FROM t3;
+-----+--------+
| num | val |
+-----+--------+
| 1 | 東京 |
| 2 | 大阪 |
| 3 | 奈良 |
+-----+--------+
3 rows in set (0.00 sec)
mysql57> ALTER TABLE t3 ADD check_val tinyint AS (IF (val IN ('東京', '大阪'), 1, NULL)) NOT NULL;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql57> SELECT * FROM t3;
+-----+--------+-----------+
| num | val | check_val |
+-----+--------+-----------+
| 1 | 東京 | 1 |
| 2 | 大阪 | 1 |
| 3 | 奈良 | 0 |
+-----+--------+-----------+
3 rows in set (0.00 sec)
mysql57> INSERT INTO t3 (num, val) VALUES (4, '京都');
ERROR 1048 (23000): Column 'check_val' cannot be null
generated columnの種類を指定しない場合、暗黙のデフォルトとしてVIRTUALになるので、
「VIRTUALなgenerated columnの場合は、今までのものは無視されて、新たに更新するものに対して有効」(VIRTUALなgenerated columnはALTER TABLEの時には何もせず、SELECTされた時に都度関数が走るから)になり、
mysql57> ALTER TABLE t3 DROP check_val;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql57> SELECT * FROM t3;
+-----+--------+
| num | val |
+-----+--------+
| 1 | 東京 |
| 2 | 大阪 |
| 3 | 奈良 |
+-----+--------+
3 rows in set (0.00 sec)
mysql57> ALTER TABLE t3 ADD check_val tinyint AS (IF (val IN ('東京', '大阪'), 1, NULL)) STORED NOT NULL;
ERROR 1048 (23000): Column 'check_val' cannot be null
「STOREDなgenerated columnの場合は、今までのもので似非CHECK制約を通らない行がある場合、そもそもALTER TABLEが通らない」(STOREDなgenerated columnはALTER TABLEの時に関数を通って計算され、それがデータに格納されるから)になる。
おおお面白いんじゃないこれ?
PlanetMySQL Voting: Vote UP / Vote DOWN