0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

データベースの利用

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

データベースの操作

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

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

まず、インストールした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

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
続いて、コードを書き換えて「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

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

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
この状態で、「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

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
また、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

当てはまるレコードが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

学習に使用した教材

・『入門】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

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?