Go言語でCSVファイルからデータベースにデータを挿入する方法を徹底解説

## はじめに
Go言語を使ってCSVファイルからデータを読み込み、データベースに挿入する方法を解説します。この記事では、GinフレームワークとEnt ORMを使用して、CSVファイルのデータをPostgreSQLデータベースに保存する手順を紹介します。

なお、こちらは以下の記事の続きです。

https://qiita.com/jijimama/items/2c5ac6277be7f6d75d0f

## ディレクトリとCSVファイルの作成
```
mkdir data
```
このdataディレクトリになかに、csvファイルを作成します。

```csv:data/newspapers.csv
毎日新聞,余録
東京新聞,筆洗
```

## 初期データ挿入の記述
main.goを以下のようにします。
```go:main.go
package main

import (
	"github.com/gin-gonic/gin"
	"context"
	"encoding/csv"
	"log"
	"os"
	"github.com/joho/godotenv"
	"github.com/jijimama/newspaper-app/ent"
	_ "github.com/lib/pq"
)

func main() {
	router := gin.Default()

	// .envファイルを読み込む
	err := godotenv.Load()
	if err != nil {
		log.Fatalf("Error loading .env file: %v", err)
	}

	// 環境変数から取得
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")
	dbUser := os.Getenv("DB_USER")
	dbName := os.Getenv("DB_NAME")
	dbPassword := os.Getenv("DB_PASSWORD")

	// 接続文字列を作成
	dsn := "host=" + dbHost + " port=" + dbPort + " user=" + dbUser + " dbname=" + dbName + " password=" + dbPassword + " sslmode=disable"

    //PostgreSQLに接続
	client, err := ent.Open("postgres", dsn)
	if err != nil {
		log.Fatalf("failed opening connection to postgres: %v", err)
	}
	defer client.Close()
	// Run the auto migration tool.
	if err := client.Schema.Create(context.Background()); err != nil {
		log.Fatalf("failed creating schema resources: %v", err)
	}

	// CSVファイルからデータを読み込み、データベースに挿入
    err = loadCSVData(client, "data/newspapers.csv")
    if err != nil {
        log.Fatalf("failed loading CSV data: %v", err)
    }
	
  	router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
      		"message": "Hello, World!",
		})
  	})

  	router.Run(":8080")
}

func loadCSVData(client *ent.Client, filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    reader := csv.NewReader(file)
    records, err := reader.ReadAll()
    if err != nil {
        return err
    }

    for _, record := range records {
        name := record[0]
        columnName := record[1]

        _, err := client.Newspaper.
            Create().
            SetName(name).
            SetColumnName(columnName).
            Save(context.Background())
        if err != nil {
            return err
        }
    }

    return nil
}
```
### 補足説明
```go
reader := csv.NewReader(file)
```
```NewReader``` 関数
```go
func NewReader(r io . Reader ) * Reader
```
- ```NewReader``` は、r から読み取る新しい ```Reader``` を返します。

- - -

```go
records, err := reader.ReadAll()
```

```(*Reader) ReadAll```関数
```go
func (r * Reader ) ReadAll() (records [][] string , err error )```
```
- ```ReadAll``` は、r から残りのすべてのレコードを読み取ります。各レコードはフィールドのスライスです。呼び出しが成功すると、 ```err == io.EOF```ではなく、 ```err == nil``` が返されます。```ReadAll``` は ```EOF``` まで読み取るように定義されているため、ファイルの終わりを報告すべきエラーとして扱いません。
- - -

もう一度再起動して、データを挿入します
```
docker-compose up backend -d
```

## データの確認
コンテナに入り直接確認していきます。

```
docker compose exec db bash
psql -U postgres -d db

db=# SELECT * FROM newspapers;
 id |   name    | column_name |          created_at           |          updated_at           
----+-----------+-------------+-------------------------------+-------------------------------
  1 |  毎日新聞 | 余録        | 2024-12-24 02:23:23.075128+00 | 2024-12-24 02:23:23.075129+00
  2 |  東京新聞 | 筆洗        | 2024-12-24 02:23:23.076488+00 | 2024-12-24 02:23:23.076488+00
(2 rows)
```

このように表示されれば、OKです！


## 参考文献
[ent CRUD API](https://entgo.io/ja/docs/crud)
[csv パッケージ NewReader関数](https://pkg.go.dev/encoding/csv#NewReader)
[csv パッケージ ReadAll関数](https://pkg.go.dev/encoding/csv#Reader.ReadAll)