グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



goでデータベースから取得したデータをcsvにして出力する

最近業務で大量のデータをcsvに出力する必要があり、goでのcsv出力方法を調べたのでまとめます。

goでcsv出力のサンプル

goでcsvを出力するために「encoding/csv」が用意されています。

参考:Package csv

「encoding/csv」を使って簡単なcsvを出力する公式ドキュメントサンプルは以下です。

package main

import (
	"encoding/csv"
	"log"
	"os"
)

func main() {
	records := [][]string{
		{"first_name", "last_name", "username"},
		{"Rob", "Pike", "rob"},
		{"Ken", "Thompson", "ken"},
		{"Robert", "Griesemer", "gri"},
	}

	w := csv.NewWriter(os.Stdout)

	for _, record := range records {
		if err := w.Write(record); err != nil {
			log.Fatalln("error writing record to csv:", err)
		}
	}

	// Write any buffered data to the underlying writer (standard output).
	w.Flush()

	if err := w.Error(); err != nil {
		log.Fatal(err)
	}
}

この例では2次元の文字列型のスライスを用意し、 csv.NewWriter にio.Writerを 実装している標準出力「os.Stdout」を渡してcsvのWriterを取得しています。 forでスライスを1行ずつ w.Write で書き込み、最後に w.Flush でバッファに 残っているデータをすべて書き込みます。

実行結果は以下です。

$ go run main.go
first_name,last_name,username
Rob,Pike,rob
Ken,Thompson,ken
Robert,Griesemer,gri

この例では1行ずつ書き込んでいますが上記の例のようにあらかじめ2次元の スライスがある場合には WriteAll でまとめて出力することも可能です。

package main

import (
	"encoding/csv"
	"log"
	"os"
)

func main() {
	records := [][]string{
		{"first_name", "last_name", "username"},
		{"Rob", "Pike", "rob"},
		{"Ken", "Thompson", "ken"},
		{"Robert", "Griesemer", "gri"},
	}

	w := csv.NewWriter(os.Stdout)
	w.WriteAll(records) // calls Flush internally

	if err := w.Error(); err != nil {
		log.Fatalln("error writing csv:", err)
	}
}

このようにencoding/csvは簡単に利用できます。

 csv.NewWriter にはio.Writerを実装していればなんでも渡せるので、 以下のようにFileを渡せばcsvをファイルに書きだせます。

file, err := os.Create("sample.csv")
if err != nil {
	panic(err)
}
defer file.Close()
cw := csv.NewWriter(file) 
defer cw.Flush()

encoding/csvで指定できるパラメータ

 csv.NewWriter で返されるWriter構造体は以下のように定義されています。

// A Writer writes records using CSV encoding.
//
// As returned by NewWriter, a Writer writes records terminated by a
// newline and uses ',' as the field delimiter. The exported fields can be
// changed to customize the details before the first call to Write or WriteAll.
//
// Comma is the field delimiter.
//
// If UseCRLF is true, the Writer ends each output line with \r\n instead of \n.
//
// The writes of individual records are buffered.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.  Any errors that occurred should
// be checked by calling the Error method.
type Writer struct {
Comma   rune // Field delimiter (set to ',' by NewWriter)
UseCRLF bool // True to use \r\n as the line terminator
w       *bufio.Writer
}

デフォルトで区切り文字は「,」が設定されており、区切り文字を変えたい場合は以下の用にCommaにruneを設定します。

w := csv.NewWriter(file)
w.Comma = '|'

また、改行文字をCRLF(\r\n)にする場合は以下のようにUseCRLFにtrueを設定します。

w := csv.NewWriter(file)
w.UseCRLF = true

データベースから取得したデータをcsvに出力してみる

今回はサンプルとして以下のテーブルを用意しました。

mysql> desc users;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(30)  | NO   |     | NULL    |                |
| last_name  | varchar(30)  | NO   |     | NULL    |                |
| tel        | varchar(30)  | NO   |     | NULL    |                |
| email      | varchar(200) | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)

上記にテーブルにfakerなどを使って適当にサンプルデータを入れておきます。 usersテーブルの内容をcsvで出力するgoのサンプルコードは以下になります。

package main

import (
	"database/sql"
	"encoding/csv"
	"fmt"
	"os"

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

func main() {
	var db *sql.DB
	var err error
	db, err = sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", "ユーザ名", "パスワード", "ホスト", "ポート番号", "db名"))
	if err != nil {
		panic(err)
	}
	defer db.Close()

	err = db.Ping()
	if err != nil {
		panic(err)
	}

	file, err := os.Create("sample.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	cw := csv.NewWriter(file)
	defer cw.Flush()

	var id int
	var firstName, lastName, tel, email string
	rows, err := db.Query("select * from users")
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	for rows.Next() {
		err := rows.Scan(&id, &firstName, &lastName, &tel, &email)
		if err != nil {
			panic(err)
		}
		col := []string{firstName, lastName, tel, email}
		cw.Write(col)
	}

	err = rows.Err()
	if err != nil {
		panic(err)
	}
}

Flushを忘れてはいけないので csv.NewWriter を呼び出した直後にdeferでFlushを指定しておきました。

上記を実行すると「sample.csv」ファイルが作成されます。
作成されたファイルをみてみると・・・

$ go run main.go
$ cat sample.csv
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
Damaris,Eichmann,821-647-1093,bpYZUis@YfomxEY.net
・・・・

csvが出力されました。

今回はutf8でそのまま出力していますが、データに日本語を含みcsvをエクセルで開きたい場合はShiftJISで出力する必要があります。
その場合はNewWriterに渡す前にtransformで文字コードを指定します。
また改行コードもCRLFを指定します。

利用する為にインストールします

$ go get golang.org/x/text/encoding/japanese
$ go get golang.org/x/text/transform
w := csv.NewWriter(transform.NewWriter(file, japanese.ShiftJIS.NewEncoder()))
w.UseCRLF = true

おわりに

encoding/csvを利用して効率よくcsvが出力できるのでとても便利でした。
csv.NewWriterに http.ResponseWriter を渡してそのままダウンロードさせることも可能なので今後もいろいろとio.Writerを組み合わせて利用していきたいと思います。

この記事を書いた人

アーティス
アーティス
創造性を最大限に発揮するとともに、インターネットに代表されるITを活用し、みんなの生活が便利で、豊かで、楽しいものになるようなサービスやコンテンツを考え、創り出し提供しています。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします