Edited at

Goでもpandasっぽいことできる! DB接続編


はじめに

前回では、GoのDataFrameの基本的な扱い方を紹介しました。

今回はDB接続して、DataFrameに入れてみようと思います。

(SQLでやったらとか言う野暮なツッコミは無しで(^人^)オ・ネ・ガ・イ)


LoadStructsでやってみる

一応ハンズラボのアドベントカレンダーなので、DBから東急ハンズの店舗情報を取得します。

LoadStructsが個人的に一番DataFrameを作りやすいのでやってみました。


main.go

package main

import (
"database/sql"
"fmt"

_ "github.com/go-sql-driver/mysql"
"github.com/kniren/gota/dataframe"
)

// DBから取得するカラムを定義
type Store struct {
ID int
Name string
Address string
}

func main() {
db, err := sql.Open("mysql", "<user>:<password>@tcp(<host>:3306)/<db>")
if err != nil {
panic(err.Error())
}
defer db.Close()

rows, err := db.Query("SELECT id, name, address FROM store")
if err != nil {
panic(err.Error())
}
defer rows.Close()

user := []Store{}

for rows.Next() {
var store Store
err := rows.Scan(&(store.ID), &(store.Name), &(store.Address))
if err != nil {
panic(err.Error())
}
user = append(user, store)
}

if err := rows.Err(); err != nil {
panic(err.Error())
}

df := dataframe.LoadStructs(user)

fmt.Println(df)
}


[84x3] DataFrame

ID Name Address
0: 1 渋谷店 渋谷区宇田川町12-18
1: 4 江坂店 吹田市豊津町9-40
2: 5 町田店 町田市原町田6-4-1東急ツインズ イースト 6F・7F
3: 6 池袋店 豊島区東池袋1-28-10
4: 7 三宮店 神戸市中央区下山手通2-10-1
5: 8 横浜店 横浜市西区南幸1-3-1横浜モアーズ 5F〜7F
6: 9 広島店 広島市中区八丁堀16-10
7: 10 新宿店 渋谷区千駄ヶ谷5-24-2タイムズスクエアビル 2F〜8F
8: 11 札幌店 札幌市中央区南一条西6-4-1
9: 12 心斎橋店 大阪市中央区南船場3-4-12
... ... ...
<int> <string> <string>

やっていることはこのような流れ



  1. Structで取得してくるカラムを定義する

  2. DB接続して、クエリを実行しデータを取得(Struct)

  3. データをforで回してスライスにStructを詰める


  4. dataframeLoadStructsにスライスを入れて読ませる


面倒な点



  • Structで取得してくるカラムを定義しないといけない


  • Structを変更したらクエリも変更しないといけない


Structを定義せずにDataFrameを作成する

dataframeLoadStructsを使用しましたが、今回はLoadRecordsを使用してDataFrameを作成します。

LoadRecordsの詳細は前回の記事を見てください。


main.go

package main

import (
"database/sql"
"fmt"

_ "github.com/go-sql-driver/mysql"
"github.com/thimi0412/gota/dataframe"
)

func main() {
stores := [][]string{}
db, err := sql.Open("mysql", "<user>:password@tcp(<host>:3306)/<db>")
if err != nil {
panic(err.Error())
}
defer db.Close()

// 追加でurlも選択します
rows, err := db.Query("SELECT id, name, address, url FROM store")
if err != nil {
panic(err.Error())
}

// カラムを取得してstoresに詰める
columns, err := rows.Columns()
if err != nil {
panic(err.Error())
}
// 一つ目の要素がカラム名となる
stores = append(stores, columns)

values := make([]sql.RawBytes, len(columns))
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}

for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
panic(err.Error())
}

var value string

// 取得した値をスライスに詰める
store := []string{}
for _, col := range values {
if col == nil {
value = "NULL"
} else {
value = string(col)
}
store = append(store, value)
}
// 作成したスライスをスライスに詰める
stores = append(stores, store)
}

df := dataframe.LoadRecords(stores)
fmt.Println(df)
}


[84x4] DataFrame

id name address url
0: 1 渋谷店 渋谷区宇田川町12-18 https://shibuya.tokyu-hands.co.jp/
1: 4 江坂店 吹田市豊津町9-40 https://esaka.tokyu-hands.co.jp/
2: 5 町田店 町田市原町田6-4-1東急ツインズ イースト 6F・7F https://machida.tokyu-hands.co.jp/
3: 6 池袋店 豊島区東池袋1-28-10 https://ikebukuro.tokyu-hands.co.jp/
4: 7 三宮店 神戸市中央区下山手通2-10-1 https://sannomiya.tokyu-hands.co.jp/
5: 8 横浜店 横浜市西区南幸1-3-1横浜モアーズ 5F〜7F https://yokohama.tokyu-hands.co.jp/
6: 9 広島店 広島市中区八丁堀16-10 https://hiroshima.tokyu-hands.co.jp/
7: 10 新宿店 渋谷区千駄ヶ谷5-24-2タイムズスクエアビル 2F〜8F https://shinjuku.tokyu-hands.co.jp/
8: 11 札幌店 札幌市中央区南一条西6-4-1 https://sapporo.tokyu-hands.co.jp/
9: 12 心斎橋店 大阪市中央区南船場3-4-12 https://shinsaibashi.tokyu-hands.co.jp/
... ... ... ...
<int> <string> <string> <string>

コードが長くなりましたがやっていることは

1. DB接続して、クエリを実行しデータを取得

2. スライスを作成

3. カラムを取得してスライスに入れる(こいつがヘッダ)

4. 選択したカラムの値を入れたスライスを2.で作成したスライスに入れる

5. LoadRecordsに読ませる


手が届かないところ

LoadRecordsは型を指定してDataFrameで読み込めないので、例えばid001だった場合LoadRecordsで読み込むと上の出力結果のようにid1になってしまいます。 

型をしっかりとしてしたい場合はLoadStructをしようした方がいいでしょう。

ハンズラボ Advent Calendar 2018 明日8日目は@naokiurです!