mysqlのトリガーを使用して他テーブルにデータを作成/更新/削除する

# 前提
mysqlのテーブル定義を変えたいけど影響が大きくすぐには難しいという課題がありました。
そのため別テーブルを作成しておいてデータは自動で同期しておきたい、というときにトリガーを使用する方法を見つけたので検証します。
AWSのRDSを使用しているので、mysqlのバージョンは5.7を使用します。

# 環境
- macOS: 12.6
- mysql: 5.7

# 準備

## docker-compose.ymlでmysqlを準備

以下のようにdocker-compose.ymlを作成してmysqlを用意します。
```yml:docker-compose.yml
version: '3.8'

services:
  db:
    image: mysql:5.7
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: sample_db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - 3306:3306
```

## データベースに接続
私はdbeaverというツールを用いてデータベースに接続しました。


https://dbeaver.io/

こちらの情報で接続することができます。
<img width="600" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/a67ba970-88aa-924e-aabb-07107df3fd9d.png">

## テーブルを作成
データコピー元のテーブル`origin_table`とコピー先のテーブル`copied_table`を作成します。
`origin_table.num_data`カラムに入る数字を`copied_table.varchar_data`カラムへコピーしますが、そのとき数字を文字列に変換するようにトリガーで設定します。

（IDのみ大文字になってしまってました…。）

```sql:origin_table
CREATE TABLE sample_db.origin_table (
	ID BIGINT auto_increment NOT NULL COMMENT 'ID',
	num_data INTEGER DEFAULT 0 NOT NULL,
	create_user varchar(100) NOT NULL,
	create_datetime DATETIME NOT NULL,
	update_user varchar(100) NOT NULL,
	update_datetime DATETIME NOT NULL,
	CONSTRAINT origin_table_PK PRIMARY KEY (ID)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_general_ci;
```

```sql:copied_table
CREATE TABLE sample_db.copied_table (
	ID bigint(20) auto_increment NOT NULL COMMENT 'ID',
	varchar_data varchar(100) NOT NULL,
	create_user varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
	create_datetime datetime NOT NULL,
	update_user varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
	update_datetime datetime NOT NULL,
	CONSTRAINT `PRIMARY` PRIMARY KEY (ID)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_general_ci;
```

# トリガーの作成

## データ作成時に実行されるトリガー

`origin_table.num_data`を`copied_table.varchar_data`にコピーする際、IF文で分岐して一度変数に格納するようにしました。これはプログラム内のenumのようなものでデータを入れる想定なので、その通り分岐するようにしています。
`NEW`はレコードへのSQL文を実行した後の値を指しています。`NEW.ID`はデータ登録後にできたレコードのIDを指します。

```sql:origin_table_insert_trigger
DROP TRIGGER IF EXISTS sample_db.origin_table_insert_trigger;
DELIMITER $$
$$
CREATE TRIGGER origin_table_insert_trigger AFTER INSERT ON origin_table FOR EACH ROW
BEGIN 
	DECLARE varchar_data_tmp varchar(100);
	IF NEW.`num_data`=1 THEN
		SET varchar_data_tmp='ONE' ;
	ELSEIF NEW.`num_data`=2 THEN
		SET varchar_data_tmp='TWO' ;
	ELSE
		SET varchar_data_tmp='OTHER' ;
	END IF;
	
	INSERT INTO copied_table SET
		`ID`=NEW.`ID`,
		`varchar_data`=varchar_data_tmp,
		`create_user`=NEW.`create_user`,
		`create_datetime` =NEW.`create_datetime`,
		`update_user` =NEW.`update_user`,
		`update_datetime`=NEW.`update_datetime`;
END$$
DELIMITER ;
```

ためしにデータを登録すると以下の通りデータがコピーされます。データの変換もできています。
<img width="800" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/3a93e9d6-a011-09ed-7f99-3bca58322b2a.png">

## データ更新時に実行されるトリガー
INSERT文とは異なり、WHERE句でIDを指定しています。

```sql:origin_table_update_trigger
DROP TRIGGER IF EXISTS sample_db.origin_table_update_trigger;
DELIMITER $$
$$
CREATE TRIGGER origin_table_update_trigger AFTER UPDATE ON origin_table FOR EACH ROW
BEGIN 
	DECLARE varchar_data_tmp varchar(100);
	IF NEW.`num_data`=1 THEN
		SET varchar_data_tmp='ONE' ;
	ELSEIF NEW.`num_data`=2 THEN
		SET varchar_data_tmp='TWO' ;
	ELSE
		SET varchar_data_tmp='OTHER' ;
	END IF;
	
	UPDATE copied_table SET
		`varchar_data`=varchar_data_tmp,
		`create_user`=NEW.`create_user`,
		`create_datetime` =NEW.`create_datetime`,
		`update_user` =NEW.`update_user`,
		`update_datetime`=NEW.`update_datetime`
	WHERE `ID` =NEW.`ID`;
END$$
DELIMITER ;
```

ID以外のデータを更新してみます。こちらもデータの変換がしっかりできています。
<img width="800" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/5eb30c4c-c008-8e5e-9713-8b5110f2c748.png">

## データ削除時に実行されるトリガー

注意点として、DELETE時に実行されるトリガーは`NEW`を使用できないことです。`NEW`はレコードに変化があった後の値なので、削除実行後は存在しなくなるためです。
```sql:origin_table_delete_trigger
DROP TRIGGER IF EXISTS sample_db.origin_table_delete_trigger;
DELIMITER $$
$$
CREATE TRIGGER origin_table_delete_trigger AFTER DELETE ON origin_table FOR EACH ROW
BEGIN 	
	DELETE FROM copied_table WHERE `ID`=OLD.`ID`;
END$$
DELIMITER ;
```

削除してみると該当のデータが消えていることがわかります。
<img width="800" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2433961/a0b485aa-034c-59e7-02b8-4399ec0cabb1.png">

# さいごに
データを変換する処理のところは少し骨が折れましたが、これでうまく動作しました。
トリガー定義を資源管理することや、ログなどを適切に管理することができれば、SQL文実行前後で何か処理をしたい場合の有効な方法の1つとなり得ます。ぜひ使っていきたいと思います。