LoginSignup
8
2

More than 3 years have passed since last update.

database/sql で INSERT したレコードのIDを取得する

Last updated at Posted at 2020-01-04

概要

Go の database/sql を利用して、 INSERT したレコードのIDを取得する

参考

問題

  • 使用しようとした lib/pq では LastInsertId() (int64, error) が使えない
  • database/sqlint64 で ID を返す Interface しか存在しない

使用しようとした lib/pq では LastInsertId() (int64, error) が使えない

database/sql では import する Driver を差し替えることで、異なる SQL Database を利用する場合でも、共通の Interface を利用して SQL を扱える。

今回利用しようとしたのは、 PostgreSQL 用の Driver の一つである lib/pq

しかしながら、 Result Interface のコメントを読むと下記が書かれており、全てのデータベースでのサポートではないことがわかる。また実際に lib/pq でこのメソッドを呼び出そうとしてもエラーが返り、 lib/pq では INSERT したレコードの ID を取得できなかった。

// LastInsertId returns the integer generated by the database
// in response to a command. Typically this will be from an
// "auto increment" column when inserting a new row. Not all
// databases support this feature, and the syntax of such
// statements varies.

database/sqlint64 で ID を返す Interface しか存在しない

また LastInsertId() は auto increment された int64 型の ID を取得する Interface であり、 ID を UUID 型に指定した場合などは、利用できないことがわかる。

解決策

Instead, we get around this by making a few changes. First, we are going to update our SQL statement by adding RETURNING id to the end of the statement. This will tell our SQL database that we want it to return the id of the newly inserted record so that we can do something with it.

その為、まず SQL 側の解決策として、 Result Interface では LastInsertID を利用するのではなく、 SQLを変更して、 Insert した後の レコードの ID を返却するように変更する。

2つ目に Go のコード側も変更する。
database/sql では UPDATE・INSERT は基本的に ~.Exec を利用して、 Result, error を取得し、 SELECT の場合は、 ~.Query を利用する。

下記は元のコード。

    stmt, err := prepare(repo, "INSERT INTO users(username, password, email) VALUES($1, $2, $3);")
    defer stmt.Close()
    if err != nil {
        return nil, fmt.Errorf("query prepare faild: %w", err)
    }

    _, err = stmt.Exec(user.Personal.Name, user.Password, user.Personal.Email)
    if err != nil {
        return nil, fmt.Errorf("exec faild: %w", err)
    }

これを下記に変更する。
QueryRow を使用することで、 Row が返り値として戻ってくるようになり、 ~ RETURNING user_id; の部分を実行結果として、受け取れるようになる。あとは Row.Scan() して、レコードの値を読み取ることで、 INSERT したレコードの ID を受け取ることができる。


    stmt, err := prepare(repo, "INSERT INTO users(username, password, email) VALUES($1, $2, $3) RETURNING user_id;")
    defer stmt.Close()
    if err != nil {
        return nil, fmt.Errorf("query prepare faild: %w", err)
    }

    err = stmt.QueryRow(user.Personal.Name, user.Password, user.Personal.Email).Scan(&user.Id)
    if err != nil {
        return nil, fmt.Errorf("query faild: %w", err)
    }

これで、 LastInsertId() 介さないので、 Driver が対応していなくてもよく、また ID の形式も int でなくてもよくなった。

以上、慣れないのでどうするのかわからなかった内容を備忘録として。
誰かの参考になれば幸いです。

8
2
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
8
2