自由気ままにブログ

地方零細企業プログラマがひっそりとなにかしています。

構造体無しで、DB情報を取得する

f:id:nono138:20200421225917p:plain

最近、自作PCでめっきりコードを書かなくなったので、久しぶりのGo言語です。

 

以前、書いた構造体を用いてDBの情報を構造体に格納する方法を書きましたが、あれはnullが入ると処理が止まってしまったり、Scan関数の引数が膨大な数になったりと言う問題はあります。

以前のDB情報の取り方

 

nono138.hatenablog.com

 

 

今回書くコードはそこら辺を解消してくれます。

 サンプルコード

package main

import (
    "database/sql"
    "fmt"
    "os"

    _ "github.com/mattn/go-sqlite3"
)

func DbSelect_exec(db *sql.DB, cmd string) (err error) {
    _err = db.Exec(cmd)
    if err != nil {
        fmt.Println(err)
        return err
    }
    return nil
}
func main() {
    DBerr := sql.Open("sqlite3""sample.db")
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    //テーブルデータ取得
    cmd := "SELECT * FROM userinfo"
    rows_ := DB.Query(cmd)

    defer rows.Close()

    // カラム情報を取得する
    types_ := rows.ColumnTypes()
    data := make(interface{}, len(types))

    // カラム数分だけポインターを保管するinterfaceを用意
    dataPtrs := make(interface{}, len(types))
    //dataPtrsにアドレスを受け渡し(nil対応の為)
    for _columntype := range types {
        fmt.Printf("Column name: %s\n", columntype.Name())
    }
    err = DbSelect_exec(DB, cmd)
    if err != nil {
        fmt.Println(err)
    }
    for i := range data {
        //pointerを渡し、可読データに変換
        dataPtrs[i] = &data[i]
    }

    for rows.Next() {
        err := rows.Scan(dataPtrs...)
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println(data)
    }
}

 

結果

Column name: Chord
Column name: UserName
Column name: Category
Column name: RecordDate
[1 テスト 太郎 男性 1970-08-22 19:13:38 +0000 UTC]
[2 テスト 花子 女性 1970-08-22 19:13:38 +0000 UTC]

 

解説

コメントで書いてあるとおり、rows.ColumnType()でカラムの情報を行単位で確保します。

dataはカラムの分だけのcapを持ったinterfaceをあらかじめ作っておきます。

下のdataPtrsも同じですね。こちらはポインタ用の為に作っています。

dataPtrs[i] = &data[i]でポインタを代入することでアドレスが参照元のアドレスが一緒になり、dataPtrsのアドレスを可読可能データにしています。

 

rows.Scan(dataPtrs...)でdataPtrsの分だけ、スキャンします。

あとは、ちゃんと情報が取れているか、出力で確認しているだけです。

 

また、interface配列で受け取ってあげるのでnil対応となっているので、データの途中でnilが入っても処理は続くようになっています。

 

mattnさんのドライバーが恐らく、nil対応になっているので今回はゴミデータを入れていませんが、SQL serverでは動作確認済です。

他のDBで確認しても良いのですが、面倒なのでご愛敬でお願い致します。

 

 個人的書き置き場

GWに入って、久しぶりにGoを書いた気がします。

ベンチマークで測定していないので、恐らく構造体で渡してあげた方がコストは安いんじゃないかな?

たぶん、メモリーもかなり食うので、実際に使う時は、小規模なデータを想定しないと使えないかもです。

私の場合は、帳票の自動作成などに使っているので、そこまで大きなデータを扱っていない時に採用しています。

また、interfaceで受け取っているので、ゴミデータが入っても処理は続いてしまうので、そこら辺も注意ですね。