データ更新

この章では、データベースのデータを変更する操作と、更新結果を取得する方法について説明します。

挿入

挿入は簡単で、selectと同様に動作します。ここでは、userテーブルに行を挿入するExecutorを作成するメソッドを定義します。

def insertUser(name: String, email: String): Executor[IO, Int] =
  sql"INSERT INTO user (name, email) VALUES ($name, $email)"
    .update

行を挿入してみよう。

insertUser("dave", "dave@example.com").commit.unsafeRunSync()

そして読み返す。

sql"SELECT * FROM user"
  .query[(Int, String, String)] // Query[IO, (Int, String, String)]
  .to[List]                     // Executor[IO, List[(Int, String, String)]]
  .readOnly(conn)               // IO[List[(Int, String, String)]]
  .unsafeRunSync()              // List[(Int, String, String)]
  .foreach(println)             // Unit

更新

更新も同じパターンだ。ここではユーザーのメールアドレスを更新する。

def updateUserEmail(id: Int, email: String): Executor[IO, Int] =
  sql"UPDATE user SET email = $email WHERE id = $id"
    .update

結果の取得

updateUserEmail(1, "alice+1@example.com").commit.unsafeRunSync()

sql"SELECT * FROM user WHERE id = 1"
  .query[(Int, String, String)] // Query[IO, (Int, String, String)]
  .to[Option]                   // Executor[IO, List[(Int, String, String)]]
  .readOnly(conn)               // IO[List[(Int, String, String)]]
  .unsafeRunSync()              // List[(Int, String, String)]
  .foreach(println)             // Unit
// Some((1,alice,alice+1@example.com))

自動生成キー

インサートする際には、新しく生成されたキーを返したいものです。まず、挿入して最後に生成されたキーをLAST_INSERT_IDで取得し、指定された行を選択するという難しい方法をとります。

def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] =
  for
    _    <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".update
    id   <- sql"SELECT LAST_INSERT_ID()".query[Int].unsafe
    task <- sql"SELECT * FROM user WHERE id = $id".query[(Int, String, String)].to[Option]
  yield task
insertUser("eve", "eve@example.com").commit.unsafeRunSync()

これは苛立たしいことだが、すべてのデータベースでサポートされている(ただし、「最後に使用されたIDを取得する」機能はベンダーによって異なる)。

MySQLでは、AUTO_INCREMENTが設定された行のみが挿入時に返すことができます。上記の操作を2つのステートメントに減らすことができます

自動生成キーを使用して行を挿入する場合、returningメソッドを使用して自動生成キーを取得できます。

def insertUser(name: String, email: String): Executor[IO, (Int, String, String)] =
  for
    id   <- sql"INSERT INTO user (name, email) VALUES ($name, $email)".returning[Int]
    user <- sql"SELECT * FROM user WHERE id = $id".query[(Int, String, String)].to[Option]
  yield user
insertUser("frank", "frank@example.com").commit.unsafeRunSync()

バッチ更新

バッチ更新を行うには、NonEmptyListを使用して複数の行を挿入するinsertManyUserメソッドを定義します。

def insertManyUser(users: NonEmptyList[(String, String)]): Executor[IO, Int] =
  val value = users.map { case (name, email) => sql"($name, $email)" }
  (sql"INSERT INTO user (name, email) VALUES" ++ values(value)).update

このプログラムを実行すると、更新された行数が得られる。

val users = NonEmptyList.of(
  ("greg", "greg@example.com"),
  ("henry", "henry@example.com")
)

insertManyUser(users).commit.unsafeRunSync()