Embulkと日付データ(その2: PostgreSQLへのデータ投入) 

EmbulkとPostgreSQLと使う場合の注意点メモ

## 環境

* embulk: 0.6.16
* embulk-output-postgresql: 0.4.1
* PostgreSQL: 9.4

## まとめ

PostgreSQLに日付データを入れる場合の注意事項

* インサート先のカラムが**timezone型(WITHOUT TIME ZONE)**の場合、設定ファイルで忘れずにタイムゾーンを指定する。
* 未指定の場合、PostgreSQL側のタイムゾーンが日本になっていても、プレビューで表示されたUTCの値がそのまま入ってしまう
* **timezone型(WITH TIME ZONE)**の場合は、embulkがタイムゾーンを考慮してくれる。
* Embulkのtimestamp型を、date型に投入する場合はWITHOUT TIME ZONEと同様にtimezoneを指定する。

## 解説

[Embulkと日付データ(その1: CSVの入力データ)](http://qiita.com/hiroysato/items/7e89ab892baacaec327e) で使ったCSVデータをそのまま用いる

CSVパーサーで、default_timezoneを指定した「2015-01-27 19:23:49」のデータは、``embulk preview``では、UTC時刻「2015-01-27 10:23:49 UTC」と表示される。

このデータをPostgreSQLに投入した場合の動作を確認する

```
embulk preview -G config.yaml
*************************** 1 ***************************
      id (     long) : 1
 account (     long) : 32,864
    time (timestamp) : 2015-01-27 10:23:49 UTC
purchase (timestamp) : 2015-01-26 15:00:00 UTC
 comment (   string) : embulk
```



### PostgreSQL側のタイムゾーンの確認

データを投入する先のPostgreSQLのサーバのタイムゾーンはJapanに指定している。

PostgreSQLのタイムゾーン設定は次のSQLで確認できる。
出力された日付が**+09**と表示されており、日本時間になっていることが確認できる

```
select now();
              now              
-------------------------------
 2015-07-15 16:17:12.120466+09
(1 row)
```

### PostgreSQLのtimezone型

* PostgreSQLのtimestamp型には、「タイムゾーンあり(WITH TIME ZONE)」と「タイムゾーンなしWITHOUT TIME ZONE」という型ががある。
* SQL標準の場合、timestamp型はタイムゾーンなしだそうだ。[参考](https://www.postgresql.jp/document/9.4/html/datatype-datetime.html#DATATYPE-TIMEZONES)


## timestamp WITHOUT TIMEZONEにデータを投入する

次のSQLを流して、インサート先のテーブルを作成する。

```sql
DROP TABLE IF EXISTS "public"."embulk_example";
CREATE TABLE "public"."embulk_example" (
	"id" int8,
	"account" int8,
	"time" timestamp(6) WITHOUT TIME ZONE,
	"purchase" timestamp(6) WITHOUT TIME ZONE,
	"comment" text COLLATE "default"
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."embulk_example" OWNER TO "postgres";
```

### embulkの設定

次のような設定を用意する。

```yaml
out:
  type: postgresql
  host: host
  user: user
  password: xxxxx
  database: database
  table: embulk_example
  mode: insert
```

### データの投入

``embulk run``でデータを投入する

```
embulk run config.yml 
2015-07-15 16:13:39.142 +0900: Embulk v0.6.16
2015-07-15 16:13:41.364 +0900 [INFO] (transaction): Loaded plugin embulk-output-postgresql (0.4.1)
2015-07-15 16:13:41.414 +0900 [INFO] (transaction): Listing local files at directory '/private/tmp/postgresql_test/pgtest/csv' filtering filename by prefix 'sample_'
2015-07-15 16:13:41.421 +0900 [INFO] (transaction): Loading files [/private/tmp/postgresql_test/pgtest/csv/sample_01.csv.gz]
2015-07-15 16:13:41.520 +0900 [INFO] (transaction): Connecting to jdbc:postgresql://host:5432/database options {user=postgres, tcpKeepAlive=true, loginTimeout=300, socketTimeout=1800}
2015-07-15 16:13:41.609 +0900 [INFO] (transaction): SQL: SET search_path TO "public"
2015-07-15 16:13:41.614 +0900 [INFO] (transaction): > 0.00 seconds
2015-07-15 16:13:41.614 +0900 [INFO] (transaction): Using insert mode
2015-07-15 16:13:41.651 +0900 [INFO] (transaction): SQL: DROP TABLE IF EXISTS "embulk_exam_1f78a400_bl_tmp000"
2015-07-15 16:13:41.656 +0900 [INFO] (transaction): > 0.00 seconds
2015-07-15 16:13:41.670 +0900 [INFO] (transaction): SQL: CREATE TABLE IF NOT EXISTS "embulk_exam_1f78a400_bl_tmp000" ("id" INT8, "account" INT8, "time" TIMESTAMP, "purchase" TIMESTAMP, "comment" TEXT)
2015-07-15 16:13:41.674 +0900 [INFO] (transaction): > 0.00 seconds
2015-07-15 16:13:41.710 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
2015-07-15 16:13:41.761 +0900 [INFO] (task-0000): Connecting to jdbc:postgresql://host:5432/database options {user=postgres, tcpKeepAlive=true, loginTimeout=300, socketTimeout=1800}
2015-07-15 16:13:41.788 +0900 [INFO] (task-0000): SQL: SET search_path TO "public"
2015-07-15 16:13:41.790 +0900 [INFO] (task-0000): > 0.00 seconds
2015-07-15 16:13:41.792 +0900 [INFO] (task-0000): Copy SQL: COPY "embulk_exam_1f78a400_bl_tmp000" ("id", "account", "time", "purchase", "comment") FROM STDIN
2015-07-15 16:13:41.966 +0900 [INFO] (task-0000): Loading 4 rows (340 bytes)
2015-07-15 16:13:41.990 +0900 [INFO] (task-0000): > 0.02 seconds (loaded 4 rows in total)
2015-07-15 16:13:41.992 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
2015-07-15 16:13:41.993 +0900 [INFO] (transaction): Connecting to jdbc:postgresql://host:5432/database options {user=postgres, tcpKeepAlive=true, loginTimeout=300, socketTimeout=28800}
2015-07-15 16:13:42.005 +0900 [INFO] (transaction): SQL: SET search_path TO "public"
2015-07-15 16:13:42.008 +0900 [INFO] (transaction): > 0.00 seconds
2015-07-15 16:13:42.009 +0900 [INFO] (transaction): SQL: INSERT INTO "embulk_example" ("id", "account", "time", "purchase", "comment") SELECT "id", "account", "time", "purchase", "comment" FROM "embulk_exam_1f78a400_bl_tmp000"
2015-07-15 16:13:42.013 +0900 [INFO] (transaction): > 0.00 seconds (4 rows)
2015-07-15 16:13:42.030 +0900 [INFO] (transaction): Connecting to jdbc:postgresql://host:5432/database options {user=postgres, tcpKeepAlive=true, loginTimeout=300, socketTimeout=1800}
2015-07-15 16:13:42.045 +0900 [INFO] (transaction): SQL: SET search_path TO "public"
2015-07-15 16:13:42.094 +0900 [INFO] (transaction): > 0.05 seconds
2015-07-15 16:13:42.095 +0900 [INFO] (transaction): SQL: DROP TABLE IF EXISTS "embulk_exam_1f78a400_bl_tmp000"
2015-07-15 16:13:42.099 +0900 [INFO] (transaction): > 0.00 seconds
2015-07-15 16:13:42.100 +0900 [INFO] (main): Committed.
2015-07-15 16:13:42.100 +0900 [INFO] (main): Next config diff: {"in":{"last_path":"/private/tmp/postgresql_test/pgtest/csv/sample_01.csv.gz"},"out":{}}
```

### 確認

一番目のエントリは、プレビュー時「2015-01-27 10:23:49 UTC」であり日本時間では、「2015-01-27 19:23:49」であるがインサートするとUTCの時間がそのままインサートされてしまっている。

```
select * from embulk_example ;
 id | account |        time         |      purchase       |          comment           
----+---------+---------------------+---------------------+----------------------------
  1 |   32864 | 2015-01-27 10:23:49 | 2015-01-26 15:00:00 | embulk
  2 |   14824 | 2015-01-27 10:01:23 | 2015-01-26 15:00:00 | embulk jruby
  3 |   27559 | 2015-01-27 17:20:02 | 2015-01-27 15:00:00 | Embulk "csv" parser plugin
  4 |   11270 | 2015-01-29 02:54:36 | 2015-01-28 15:00:00 | NULL
```

### タイムゾーンの指定

このような場合out側のプラグインの設定でタイムゾーンを指定する。
タイムゾーンは、「カラム毎の設定」または「デフォルトのタイムゾーン指定」をすることができる。

カラム毎の設定の場合は``column_options``でタイムゾーンを指定する。この場合時刻を利用するカラム毎に設定が必要。

```yaml
out:
  type: postgresql
  host: host
  user: user
  password: xxxxx
  database: database
  table: embulk_example
  mode: insert
  column_options:                   # <-- これ
    time: { timezone: "Japan" }     # <-- timeカラム用
    purchase: { timezone: "Japan" } # <-- purchaseカラム用

```

すべてのカラムで指定する必要がない場合は、``default_timezone``を指定すると良い

```
out:
  type: postgresql
  host: host
  user: user
  password: xxxxx
  database: database
  table: embulk_example
  mode: insert
  default_timezone: "Japan"  # <-- これ
```

これを指定して再度データをインサートすると、日本時間に変換された時刻が各カラムにインサートされることが確認できる

```
select * from embulk_example ;
 id | account |        time         |      purchase       |          comment           
----+---------+---------------------+---------------------+----------------------------
  1 |   32864 | 2015-01-27 19:23:49 | 2015-01-27 00:00:00 | embulk
  2 |   14824 | 2015-01-27 19:01:23 | 2015-01-27 00:00:00 | embulk jruby
  3 |   27559 | 2015-01-28 02:20:02 | 2015-01-28 00:00:00 | Embulk "csv" parser plugin
  4 |   11270 | 2015-01-29 11:54:36 | 2015-01-29 00:00:00 | NULL
(4 rows)
```

## timestamp WITH TIME ZONEにデータを投入する

```sql
DROP TABLE IF EXISTS "public"."embulk_example";
CREATE TABLE "public"."embulk_example" (
	"id" int8,
	"account" int8,
	"time" timestamp(6) WITH TIME ZONE,
	"purchase" timestamp(6) WITH TIME ZONE,
	"comment" text COLLATE "default"
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."embulk_example" OWNER TO "postgres";
```

PostgreSQL固有のtimestamp WITH TIMEZONE型の場合、default_timezoneやcolumn_optionsを指定しなくても、タイムゾーンを考慮してデータをインサートしてくれる。

``mode: insert_direct``でembulkがテーブルを自動作成する場合、デフォルトではタイムゾーンありの型で作成される。

```
 id | account |          time          |        purchase        |          comment           
----+---------+------------------------+------------------------+----------------------------
  1 |   32864 | 2015-01-27 19:23:49+09 | 2015-01-27 00:00:00+09 | embulk
  2 |   14824 | 2015-01-27 19:01:23+09 | 2015-01-27 00:00:00+09 | embulk jruby
  3 |   27559 | 2015-01-28 02:20:02+09 | 2015-01-28 00:00:00+09 | Embulk "csv" parser plugin
  4 |   11270 | 2015-01-29 11:54:36+09 | 2015-01-29 00:00:00+09 | NULL
(4 rows)
```