Go言語(プログラミング)入門メモ㉙

## データベースの利用
Goを使ってデータベースにアクセスする。SQLiteというデータベースのシステムを利用するために、go-sqlite3というパッケージを使う。

## データベースの操作
データベースはSQLで操作を行う。主なSQL文の種類としては以下。
CREATE文：テーブルを作る
INSERT文：レコードを追加する
SELECT文：レコードを取得する
UPDATE文：レコードを更新する
DELETE文：レコードを削除する

SQLでデータベースを操作するには、database/sqlパッケージを使用する。データベースを操作する大まかな流れは次の通り。
①sql.Open関数でデータベースを開く
②SQL文を作る
③SQL文を実行する
④Closeメソッドでデータベースを閉じる

まず、インストールした<foo>github.com/mattn/go-sqlite3を、_を使ってインポートする。
sqlite3はコードの中で使わないが、ビルド時に必要なため。
また、「var DbConnection ＊sql.DB」とグローバルで宣言し、データベースを利用する準備をする。
(補足)
この変数宣言は、DbConnection、という名前で「＊sql.DB」型の変数を宣言しているという意味。
sql.DB 型について、
パッケージ：database/sql パッケージに含まれている。

役割：データベースへの接続を管理し、クエリの実行やトランザクションの開始など、データベース操作のためのメソッドを提供する。

接続プール：sql.DB は内部的に接続プールを管理しており、接続の再利用や新規作成を自動的に行う。

ポインタを使う理由は、以下。
共有アクセス：ポインタを使うことで、複数の関数やメソッドから同じデータベース接続を共有できるため。

効率性：データベース接続はリソースを消費するため、一度確立した接続を再利用する方が効率的だから。

可変性：ポインタを使うと、接続先を変更したい場合などに変数自体を再代入できる。

(補足終わり)


続いてsql.Open関数でデータベースを開く。引数には「sqlite3」というデーベースのドライバ名と、「./example.sql」というファイル名を指定して、返り値をDbConnectionに代入する。この変数DbConnectionを使って、データベースを操作していく。また、最後にデータベースは閉じる必要があるので、deferで「Db.Connection.Close()」を実行する。
次に、テーブルを作成するSQL文であるCREATE文を作成する。
~~~
CREATE TABLE テーブル名 (カラム名 型, ~)
~~~
テーブルが存在しない場合のみ作成したい場合は次のようになる。
~~~
CREATE TABLE IF NOT EXISTS テーブル名 (カラム名 型, ~)
~~~
ここでは、「person」という名前のテーブルが存在しないときに「person」といいうテーブルを作成し、string型のnameとint型のageというカラムを作る。「`」で囲むことで、開業しながらSQL文を書くことができる。
また、SQL文は変数cmdに代入する。代入したら、Execメソッドを使用して「Db.Connection.Exec(cmd)」のように実行する。
CREATE文の時は返り値を使用しないので、エラーのみを受け取ってエラーハンドリングを行う。

~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()
	cmd := `CREATE TABLE IF NOT EXISTS person (
		name STRING
		age INT
		)`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}
}
~~~
上記のコードを実行するとディレクトリ内に「example.sql」というファイルが作成される。
ターミナルから「sqlite3 example.sql」を実行して対話型シェルを起動させる。「.table」というコマンドを実行すると、「person」テーブルができていることを確認できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/247ea86b-e057-9a6d-360b-c2c30b38cc38.png)

## INSERT文でレコードを挿入する
INSERT文の書き方は以下。
~~~
INSERT INTO テーブル名 (カラム名, ~) VALUES (追加する値, ~)
~~~
このとき、追加する値を「?」にしておくと、Execメソッドの実行時に値を渡すことができる。この「?」をプレースホルダという。
次のコードを実行する。
~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "INSERT INTO person (name, age) VALUES(?, ?)"
	_, err = DbConnection.Exec(cmd, "Nancy", 20)
	if err != nil {
		log.Fatalln(err)
	}
}
~~~
実行後sqlite3でテーブルの中身をselect文で確認する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/7917ff1b-9026-52e0-7585-d81421b29c73.png)
続いて、コードを書き換えて「Mike」「24」のレコードも追加する。
~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "INSERT INTO person (name, age) VALUES(?, ?)"
	_, err = DbConnection.Exec(cmd, "Mike", 24)
	if err != nil {
		log.Fatalln(err)
	}
}
~~~
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/26321c58-7615-e97d-514d-d70c38def671.png)

## UPDATE文でのレコード更新
UPDATE文の書き方は以下。更新後の値や更新箇所の条件を指定する。
~~~
UPDATE テーブル名 SET カラム名 = ~ WHERE = ~
~~~
今回はMikeのageを25に更新するために以下のコードを実行する。
~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "UPDATE  person SET age = ? WHERE name = ? "
	_, err = DbConnection.Exec(cmd, 25, "Mike")
	if err != nil {
		log.Fatalln(err)
	}
}
~~~
更新後の結果は以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/c0a7e9fb-61ee-58ea-1f18-009ee78eaf08.png)
## SELECT文で複数のレコードを取得する
INSERT文をもう一度実行し、「Nancy」「20」のレコードをもう一件挿入する。
~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "INSERT INTO person (name, age) VALUES (?, ?) "
	_, err = DbConnection.Exec(cmd, "Nancy", 20)
	if err != nil {
		log.Fatalln(err)
	}
}
~~~
実行結果は以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/b8db49b3-0cfc-498c-5d72-4de645a22178.png)
この状態で、「personテーブルのすべてのレコードを取得する」というSELECT文を作って実行する。
SELECT文で取得したレコードを得るためには、Queryメソッドを使う。なお、ここではエラーハンドリングをしない。また、Queryメソッドの返り値を代入した変数rowは、deferで「rows.Close()」を実行する必要がある。
次に、読み込んだレコードのデータを格納するための構造体Personを作成し、NameとAgeのフィールドを作る。「var pp []Person」と構造体Personのスライスを作り、ここにPersonテーブルのレコードを1件ずつ格納する。
変数rowsでNextメソッドを使い、forループで1件ずつレコードを取り出していく。ループの中で変数pを宣言した後、Scanメソッドで「rows.Scan(&p.Name, &p.Age)」として変数pのフィールドに値を入れて、変数ppのスライスにappend関数で追加する。
その後、rangeで変数ppから1つずつ値を取り出してNameとAgeを表示してみる。
次のコードを実行すると、personテーブルのすべてのレコードが出力される。
~~~
package main

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

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

var DbConnection *sql.DB

type Person struct {
	Name string
	Age  int
}

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "SELECT * FROM person"
	rows, _ := DbConnection.Query(cmd)
	defer rows.Close()
	var pp []Person
	for rows.Next() {
		var p Person
		err := rows.Scan(&p.Name, &p.Age)
		if err != nil {
			log.Println(err)
		}
		pp = append(pp, p)
	}
	for _, p := range pp {
		fmt.Println(p.Name, p.Age)
	}
}
~~~
実行結果は以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/c8cc3892-3c43-23ee-6622-05e6cd9d9a3c.png)

## SELECT文で1件のレコードを取得する
SELECT文で条件を指定する場合は、WHERE句のあとに条件を書く。「SELECT * FROM person where age = ?」とした場合、personテーブルからageカラムの値が「?」で指定した値と等しいレコードのみを取得できる。
レコードを1件だけ取得したいQueryRowメソッドを使用する。取得したレコードは、Scanメソッドで変数pに値を格納する。QueryRowメソッドではエラーハンドリングを少し変更する。エラーを格納した変数errがsql.ErrNoRows(当てはまるレコードがないときに起こるエラー)の場合は「log.Println("NO row")」を実行し、そうでない場合は、「log.Println(err)」を実行する。
次のコードを実行すると、ageカラムの値が「20」のレコードが取得される。
personテーブルには、ageカラムの値が「20」のレコードが2件存在するが、QueryRowメソッドはレコードを1件のみ取得する。
~~~
package main

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

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

var DbConnection *sql.DB

type Person struct {
	Name string
	Age  int
}

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "SELECT * FROM person where age = ?"
	row := DbConnection.QueryRow(cmd, 20)
	var p Person
	err = row.Scan(&p.Name, &p.Age)
	if err != nil {
		if err == sql.ErrNoRows {
			log.Println("No row")
		} else {
			log.Println(err)
		}
	}
	fmt.Println(p.Name, p.Age)

}
~~~
実行結果は以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/49b54a29-97c2-6da9-e6b0-925dc8c5b028.png)
また、WHERE句の条件に「1000」を指定してみる。
~~~
package main

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

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

var DbConnection *sql.DB

type Person struct {
	Name string
	Age  int
}

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "SELECT * FROM person where age = ?"
	row := DbConnection.QueryRow(cmd, 1000)
	var p Person
	err = row.Scan(&p.Name, &p.Age)
	if err != nil {
		if err == sql.ErrNoRows {
			log.Println("No row")
		} else {
			log.Println(err)
		}
	}
	fmt.Println(p.Name, p.Age)

}
~~~
実行結果は以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/d78187f8-e9bf-37cf-fe88-6658cc011e99.png)

当てはまるレコードがpersonテーブルに存在しないため、「No row」のログが出力されている。
また、最後の「fmt.Println(p.Name, p.Age)」の処理では、変数pに値が入っていないため、それぞれNameとAgeのデフォルト値(空文字と0)が表示されている。

## DELETE文でレコードを削除する
レコードを削除する場合はDELETE文を実行する。ここでは、「DELETE FROM person WHERE name = ?」として、personテーブルからnameカラムの値が「?」で指定した値と等しいレコードを削除する。Execメソッドで、「?」に「Nancy」を指定してSQL文を実行する。
~~~
package main

import (
	"database/sql"
	"log"

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

var DbConnection *sql.DB

type Person struct {
	Name string
	Age  int
}

func main() {
	DbConnection, _ := sql.Open("sqlite3", "example.sql")
	defer DbConnection.Close()

	cmd := `CREATE TABLE IF NOT EXISTS person (
        name TEXT,
        age INT
        )`
	_, err := DbConnection.Exec(cmd)
	if err != nil {
		log.Fatalln(err)
	}

	cmd = "DELETE FROM person WHERE name = ?"
	_, err = DbConnection.Exec(cmd, "Nancy")
	if err != nil {
		log.Fatalln(err)
	}

}
~~~
コードを実行した後のテーブルの確認結果は以下で「Nancy」のレコードが消えている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3579705/8df1de29-69ba-9620-ab46-1fdc82a2d437.png)



## 学習に使用した教材
・『入門】Golang基礎入門 + 各種ライブラリ + 簡単なTodoWebアプリケーション開発(Go言語)』M.A EduTech
https://www.udemy.com/course/golang-webgosql/?utm_medium=udemyads&utm_source=bene-msa&utm_campaign=responsive&utm_content=top-1&utm_term=general&msclkid=81e2f24a32cc185d275d953d60760226&couponCode=NEWYEARCAREERJP

・『シリコンバレー一流プログラマーが教える Goプロフェッショナル大全』酒井 潤 (著)
https://www.amazon.co.jp/%E3%82%B7%E3%83%AA%E3%82%B3%E3%83%B3%E3%83%90%E3%83%AC%E3%83%BC%E4%B8%80%E6%B5%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC%E3%81%8C%E6%95%99%E3%81%88%E3%82%8B-Go%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E5%A4%A7%E5%85%A8-%E9%85%92%E4%BA%95-%E6%BD%A4/dp/4046070897