GoでSQLのSELECTをする

Goの標準パッケージを使ってデータベースに接続してSELECT文を実行する方法を調べたので、簡単にまとめる。

## 準備

### 1. パッケージのインポート
抽象的な操作を提供する標準パッケージの`database/sql`と、実際にMySQLに対する操作が実装されている`go-sql-driver/mysql`をインポートする。他のパッケージは必要に応じて。

```go
import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)
```

### 2. 結果を代入する変数の宣言

```go
var (
	id   *int
	name *string
)
```

### 3. sql.DBオブジェクトの生成

使用するドライバやデータベースの認証情報を渡して`sql.DB`オブジェクトを生成する。この時点ではまだDBへのコネクションは作られておらず、コネクションは実際にクエリを発行する時に`database.sql`パッケージが勝手に生成・破棄してくれるらしい。コネクションプーリングも勝手にやってくれるとのこと。

```go
db, err := sql.Open("mysql", "<HOST>:<PASSWORD>@<PROTOCOL>(<ADDRESS>)/<DATABASE>")
if err != nil {
	log.Fatalf("failed to open db: %v", err)
}
defer db.Close()

db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
```

## SELECTの実行

以下のデータに対して操作を行う。

```sql
mysql> desc experiments;
+------------+-----------+------+-----+-------------------+-------------------+
| Field      | Type      | Null | Key | Default           | Extra             |
+------------+-----------+------+-----+-------------------+-------------------+
| id         | int       | NO   | PRI | NULL              | auto_increment    |
| network_id | int       | NO   | MUL | NULL              |                   |
+------------+-----------+------+-----+-------------------+-------------------+

mysql> select id, network_id from experiments;
+----+------------+
| id | network_id |
+----+------------+
|  1 |          1 |
|  2 |          1 |
|  3 |          2 |
+----+------------+

mysql> desc networks;
+------------+--------------+------+-----+-------------------+-----------------------------------------------+
| Field      | Type         | Null | Key | Default           | Extra                                         |
+------------+--------------+------+-----+-------------------+-----------------------------------------------+
| id         | int          | NO   | PRI | NULL              | auto_increment                                |
| name       | varchar(255) | NO   |     | NULL              |                                               |
+------------+--------------+------+-----+-------------------+-----------------------------------------------+

mysql> select id, name from networks;
+----+------+
| id | name |
+----+------+
|  1 | tcp  |
|  2 | quic |
+----+------+
```

## 複数行 / 単発

`*sql.DB.Query()`の第一引数にクエリ、第二引数以降にプレースホルダに入れる引数を渡す(もしあれば)と結果のイテレーターが返ってくるので、Next()メソッドで順に結果を取得することができる。

```go
rows, err := db.Query(
	"select e.id from experiments e join networks n on e.network_id = n.id where n.name = ?",
	"tcp",
)
if err != nil {
	log.Fatalf("failed to query: %v", err)
}
defer rows.Close()

for rows.Next() {
	if err := rows.Scan(&id); err != nil {
		log.Fatalf("failed to scan row: %s", err)
	}
	fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
	log.Fatalln(err)
}
```

結果

```
id: 1
id: 2
```

## 複数行 / 複数発

`*sql.DB.Prepare()`にプレースホルダ付きのクエリを渡して`*sql.Stmt`を得る。`*sql.Stmt.Query()`にプレースホルダに入れる値を渡すと`*sql.DB.Query()`と同様に結果のイテレーションが返ってくる。パラメータを変えて何度か同じクエリを実行したい際に使う。

```go
stmt, err := db.Prepare("select e.id from experiments e join networks n on e.network_id = n.id where n.name = ?")
if err != nil {
	log.Fatalf("failed to prepare: %v", err)
}
defer stmt.Close()

rows, err := stmt.Query("tcp")
if err != nil {
	log.Fatalf("failed to query: %v", err)
}
defer rows.Close()

fmt.Println("case tcp")
for rows.Next() {
	if err := rows.Scan(&id); err != nil {
		log.Fatalf("failed to scan row: %s", err)
	}
	fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
	log.Fatalln(err)
}

fmt.Println("case quic")
rows, err = stmt.Query("quic")
if err != nil {
	log.Fatalf("failed to query: %v", err)
}
defer rows.Close()

for rows.Next() {
	if err := rows.Scan(&id); err != nil {
		log.Fatalf("failed to scan row: %s", err)
	}
	fmt.Printf("id: %d\n", *id)
}
if err = rows.Err(); err != nil {
	log.Fatalln(err)
}

```

結果

```
case tcp
id: 1
id: 2
case quic
id: 3
```

## 単一行

単一行だけ取得する際の便利メソッドとして`*sql.DB.QueryRow()`がある。呼び出しは`*sql.DB.Query()`と同じように第一引数にクエリ、第二引数にプレースホルダーに入れるパラメータを渡す。`*sql.DB.QueryRow()`からは`*Row`オブジェクトが返ってくるので、メソッドチェーンで`Scan()`を呼ぶことでわざわざ単一行の結果に対してイテレーションを回さなくて済む。


```go
if err := db.QueryRow(`select e.id, n.name from experiments e 
join networks n on e.network_id = n.id 
where n.name = ? 
order by e.created_at desc 
limit 1`, "tcp",
).Scan(&id, &name); err != nil {
	log.Fatalf("failed to query row: %v", err)
}
fmt.Printf("id: %d, name: %s\n", *id, *name)
```

結果
```
id: 2, name: tcp
```

## 参考資料
- [Go database/sql tutorial](http://go-database-sql.org/)
- ["go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package](https://github.com/go-sql-driver/mysql)