【PostgreSQL】スーパーユーザを使わずにCOPYコマンドでCSVファイルに出力する

INDEX
----
- [概要](#概要（愚痴）)
- [目的](#目的)
- [環境](#概環境)
- [前提条件](#前提条件)
- [試行錯誤](#試行錯誤)
- [まとめ](#まとめ)
- [追記（2017/06/05）](#追記（2017/06/05）)


概要（愚痴）
----
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">PostgresのCOPY TOでCSV吐きたかったんだけど、しゃあないからSTDOUTとリダイレクトで出した。もうアホかと。<a href="https://twitter.com/hashtag/%E7%A4%BE%E7%95%9C%E3%81%A1%E3%82%83%E3%82%93%E5%8F%B0%E8%A9%9E%E3%83%A1%E3%83%BC%E3%82%AB%E3%83%BC?src=hash">#社畜ちゃん台詞メーカー</a> <a href="https://t.co/rHdsYHelTL">https://t.co/rHdsYHelTL</a> <a href="https://t.co/0aJDuizle5">pic.twitter.com/0aJDuizle5</a></p>&mdash; まあにい (@mahny_b) <a href="https://twitter.com/mahny_b/status/870509775141822465">2017年6月2日</a></blockquote>
自分にはPostgreSQLの知識は無いと共有はしてたんだけど、DBからCSVでデータが欲しいと頼まれて、でもセキュリティーポリシーから管理者権限は付与できないとか、無茶振りされたけど何とかなったので、その方法を書きます。
サックリやり方だけ見たい場合は、【[まとめ](#まとめ)】を見てください。

追記（2017/06/05）：【\copy】で出来るのでご注意。
試行錯誤やらはせっかくの黒歴史（）なので残しておきます。

目的
----
- 管理者権限がないユーザを使って、PostgreSQLのデータをCSV出力する

環境
----
```
mahny@mahny-VirtualBox:/tmp/20170604-postgres-dump$ psql -c "select version();" qiita
                                                     version                                                     
-----------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.5.7 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609, 64-bit
(1 row)
```
（※OSはLubuntu16）

前提条件
----

## アカウント情報
```
qiita=> \du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 mahny     | Create DB                                                  | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
```

## データベース情報
```
qiita=> \l
                                  List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    |   Access privileges   
-----------+----------+----------+-------------+-------------+-----------------------
 postgres  | postgres | UTF8     | ja_JP.UTF-8 | ja_JP.UTF-8 | 
 qiita     | mahny    | UTF8     | ja_JP.UTF-8 | ja_JP.UTF-8 | 
 template0 | postgres | UTF8     | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
 template1 | postgres | UTF8     | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres          +
           |          |          |             |             | postgres=CTc/postgres
(4 rows)
```

## テーブル情報
```
qiita=> \dt
         List of relations
 Schema |   Name    | Type  | Owner 
--------+-----------+-------+-------
 public | t_m_users | table | mahny
(1 row)

qiita=> select * from t_m_users;
 id | name  | updated_date 
----+-------+--------------
  1 | mahny | 2017-06-04
  2 | cia   | 2017-06-04
  3 | mary  | 2017-06-04
  4 | kite  | 2017-06-04
  5 | lucia | 2017-06-04
(5 rows)
```

試行錯誤
---
まずは、ググッてすぐに見つかる「COPY TO」で出す方法を試す。

```
qiita=> copy t_m_users to '20170604.csv' with  csv delimiter ',' force quote *;
ERROR:  must be superuser to COPY to or from a file
HINT:  Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.
```

スーパーユーザーじゃないとダメとか言ってる。
あ、でもなんかHINTって出てんね。

[公式ドキュメントのCOPY](https://www.postgresql.jp/document/9.5/html/sql-copy.html)を見ると、STDINとかSTDOUTを使うといいらしいので、試してみる。

```
qiita=> copy t_m_users TO stdout with  csv delimiter ',' force quote *;
"1","mahny","2017-06-04"
"2","cia","2017-06-04"
"3","mary","2017-06-04"
"4","kite","2017-06-04"
"5","lucia","2017-06-04"
```
あ、出た。

ってことは、対話ツールの外に持っていければいいんかな。

```
$ psql -c "copy t_m_users to stdout with csv delimiter ',' force quote *;" qiita > dump.csv
$ ll
合計 12
drwxrwxr-x  2 mahny mahny 4096  6月  4 21:30 ./
drwxrwxrwt 12 root  root  4096  6月  4 21:25 ../
-rw-rw-r--  1 mahny mahny  121  6月  4 21:30 dump.csv
$ cat dump.csv 
"1","mahny","2017-06-04"
"2","cia","2017-06-04"
"3","mary","2017-06-04"
"4","kite","2017-06-04"
"5","lucia","2017-06-04"
```

おお、うまく行った。
つまり、PostgreSQLから直接ファイルに出すのではなく、一旦画面（STDOUT）に出して、それをリダイレクトすればいいってことかー。
多分、今回試してないけどインポートも同じ考えなんだべな。

まとめ
----
1. COPY TOの結果を画面に標準出力させ
1. 画面に出力したものを、リダイレクトでファイルに書き込む

```
$ psql -c "COPY [テーブル名] TO STDOUT WITH DEMILITER ',' FORCE QUOTE *;" [DB名] > [CSVファイル名]

例)
$ psql -c "copy t_m_users to stdout with csv delimiter ',' force quote *;" qiita > dump.csv
```
（※区切り文字やらダブルクォート付きとかは適当に読み替えてね）

追記（2017/06/05）
---
通院している病院の待合室でコメントに気づいてノートでサッと検証したところあっさり出来ました。（knoguchiさん感謝）

```
$ psql -c "\copy t_m_users to 'dump.csv' with csv delimiter ',' force quote *;" qiita
COPY 5
$ ll
合計 12
drwxrwxr-x  2 mahny mahny 4096  6月  5 09:45 ./
drwxrwxrwt 10 root  root  4096  6月  5 09:45 ../
-rw-rw-r--  1 mahny mahny  121  6月  5 09:45 dump.csv
$ cat dump.csv 
"1","mahny","2017-06-04"
"2","cia","2017-06-04"
"3","mary","2017-06-04"
"4","kite","2017-06-04"
"5","lucia","2017-06-04"
```

コメントにあるクライアント動作の話も[ドキュメント](https://www.postgresql.jp/document/9.5/html/sql-copy.html)にちゃんと書いてありました。（ちゃんと読めや自分…）
ドヤ顔で書いた[まとめ](#まとめ)は「\copy」だけで実現可能。違いはクライアント側処理かDBサーバ処理かの違いという事で権限的にもとてもしっくり来た。

> COPYをpsqlの\copyと混同しないでください。 \copyはCOPY FROM STDINやCOPY TO STDOUTを呼び出し、psqlクライアントからアクセスできるファイルにデータの書き込み/読み込みを行います。 したがって、\copyコマンドでは、ファイルへのアクセスが可能かどうかと、ファイルに対するアクセス権限の有無は、サーバではなくクライアント側に依存します。

以上。