3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GoでSQLのSELECTをする

Posted at

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

準備

1. パッケージのインポート

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

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

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

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

var (
	id   *int
	name *string
)

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

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

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の実行

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

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()メソッドで順に結果を取得することができる。

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()と同様に結果のイテレーションが返ってくる。パラメータを変えて何度か同じクエリを実行したい際に使う。

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()を呼ぶことでわざわざ単一行の結果に対してイテレーションを回さなくて済む。

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

参考資料

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?