いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-364 (*sql.Rowsから[]map[string]anyに変換)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。よく忘れるのでここにメモメモ。。

Goの標準ライブラリを使って、データベースからSELECTした結果を取得する場合に Scan メソッドを使いますが

たまに、マップで欲しいときもあります。ちょっとしたツール作ってて、いちいち構造体切るのが面倒なときとか。

んで、この Scan メソッドさんにはポインタを渡さないと行けないので、必要なカラム分を一気に取得したい場合にどうするの?ってなったりします。

(私は最初なりました)

ちょっと手間ですが、スライスを2つ作って処理することが出来ます。

サンプル

package sqlmap

import "database/sql"

// MapRows maps the data in the given *sql.Rows to []map[string]any.
//
// This code is based on the ideas presented at the following URL:
//   - https://kylewbanks.com/blog/query-result-to-map-in-golang
func MapRows(rows *sql.Rows) ([]map[string]any, error) {
    var (
        cols []string
        err  error
    )

    cols, err = rows.Columns()
    if err != nil {
        return nil, err
    }

    var (
        result = make([]map[string]any, 0)
    )

    for rows.Next() {
        var (
            c  = make([]any, len(cols)) // column values
            cp = make([]any, len(cols)) // pointer of column value
        )

        for i := 0; i < len(c); i++ {
            cp[i] = &c[i]
        }

        err = rows.Scan(cp...)
        if err != nil {
            return nil, err
        }

        var (
            m = make(map[string]any)
        )

        for i, name := range cols {
            v := cp[i].(*any)
            m[name] = *v
        }

        result = append(result, m)
    }

    return result, nil
}

最初に値を格納するスライスを作って、その後にそのポインタを格納するスライスを用意します。

それを Scan メソッドに ... 付きで渡したら一気に取得できます。

テストコードは以下。

package sqlmap

import (
    "database/sql"
    "testing"

    _ "github.com/glebarez/go-sqlite"
)

func TestMapRows(t *testing.T) {
    //
    // Arrange
    //
    db, err := sql.Open("sqlite", "testdb/chinook.db")
    if err != nil {
        t.Error(err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT * FROM artists ORDER BY ArtistId DESC LIMIT 5")
    if err != nil {
        t.Error(err)
    }
    defer rows.Close()

    //
    // Act
    //
    m, err := MapRows(rows)

    //
    // Assert
    //
    if err != nil {
        t.Error(err)
    }

    if m == nil {
        t.Errorf("[want] not nil\t[got] nil")
    }

    for _, v := range m {
        t.Logf("[row] %v", v)
    }
}

実行すると以下のような結果になります。

$ task test
task: [test] go test -v ./...
=== RUN   TestMapRows
    maprows_test.go:43: [row] map[ArtistId:275 Name:Philip Glass Ensemble]
    maprows_test.go:43: [row] map[ArtistId:274 Name:Nash Ensemble]
    maprows_test.go:43: [row] map[ArtistId:273 Name:C. Monteverdi, Nigel Rogers - Chiaroscuro; London Baroque; London Cornett & Sackbu]
    maprows_test.go:43: [row] map[ArtistId:272 Name:Emerson String Quartet]
    maprows_test.go:43: [row] map[ArtistId:271 Name:Mela Tenenbaum, Pro Musica Prague & Richard Kapp]
--- PASS: TestMapRows (0.00s)
PASS
ok      github.com/devlights/sqlmap

リポジトリ

上のMapRows関数を定義してるライブラリを以下のリポジトリにアップしました。

github.com

参考情報

Goのおすすめ書籍

上の書籍の日本語版が下です。


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。