データベースのレコードの挿入を自動で行うプログラムを作っていた際に、同じデータがあったら挿入させないようにするにはどうしたらいいのかわからなかったので調べて実装してみました。
コード
実際に実行するSQLクエリは下のようになっています。
INSERT INTO musics (music_name, composer,createdAt,updatedAt)
SELECT ?,?,?,? WHERE NOT EXISTS (SELECT 1 FROM musics WHERE music_name = ?, AND composer = ?)
これはサブクエリで同じデータがないか確認し、存在しなければ実行するように条件分岐させています。
上のクエリにおけるサブクエリ内では、SELECT 1
と書かれている部分があると思います。
これは、実際には以下のように動作します。
mysql> SELECT 1 FROM musics;
+---+
| 1 |
+---+
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
| 1 |
+---+
14 rows in set (0.00 sec)
このようにカラムに1を指定すると、レコードの件数分1が返されることになります。
SELECT * よりも早いという話がありますが、結果はわからないです
それをWHERE
文で指定して件数分、1を表示させています。
加えて、WHERE NOT EXIST
文で件数が0かどうかを調べています。
件数が0であると存在するものがないということでTrueとなり、INSERT
が実行できます。件数が0より上であると存在するものがあるということでFalseとなり挿入することができません。
また、挿入の際にVALUES
ではなく、SELECT
するのは条件付き挿入を行っているためです。
今回、このクエリをGoで行ったのですが、実際に使ったコードは下のようになっています。
import (
"context"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func (m *MusicRepository) Register(ctx context.Context, music model.Music) error {
query := `INSERT INTO musics (music_name, composer,createdAt,updatedAt)
SELECT ?,?,?,? WHERE NOT EXISTS (SELECT 1 FROM musics WHERE music_name = ?, AND composer = ?)`
_, err := m.db.ExecContext(ctx, query, music.MusicName, music.Composer, time.Now(), time.Now(), music.MusicName, music.Composer)
if err != nil {
//log.Fatal("database can't insert music", err)
return fmt.Errorf("Error register music :%s", err)
}
return nil
}
?
ってなんだと思われる方がいるかもしれませんが、?の部分はプレースホルダーで、後から数値を代入できるようにしています。
そのクエリをExecContext
メソッドで実行しています。
ぜひお役に立てば嬉しいです。