※この記事にはアフィリエイト広告(プロモーション)が含まれます。
PostgreSQL で表を CSV に出したり、CSV を表に入れたりするときは COPY 文か、psql の \copy を使います。この記事の出力は PostgreSQL 18.6(Ubuntu 26.04 の標準パッケージ)で採りました。
まず見る(結論)
-- 出力(psql から。一般ユーザーでも使える)
\copy item TO '/home/user/item.csv' WITH (FORMAT csv, HEADER)
-- 取り込み
\copy item FROM '/home/user/item.csv' WITH (FORMAT csv, HEADER)
- 迷ったら
\copy。ファイルは psql を動かしているマシン・ユーザーで読み書きされる COPY ... TO 'ファイル'はサーバーのプロセス(OS ユーザーpostgres)が書く。一般ユーザーはpg_write_server_filesロールが無いと使えない- 取り込みは1行でもエラーがあると全体が取り消され、0件になる。PostgreSQL 17 以降は
ON_ERROR ignoreで型の合わない行だけ飛ばせる
COPY と \copy の違い
COPY ... TO / FROM 'ファイル' |
\copy(psql) |
|
|---|---|---|
| ファイルを読み書きするのは | DB サーバー上の postgres プロセス | psql を実行しているクライアント |
| ファイルの所有者(出力時) | postgres |
psql を実行した OS ユーザー |
| 使えるユーザー | スーパーユーザーか pg_write_server_files / pg_read_server_files を持つロール |
表の権限があれば誰でも |
| 書き方 | SQL 文。末尾に ; |
psql のメタコマンド。1行で書く |
一般ユーザーで COPY ... TO を実行すると、次のエラーになります。
ERROR: permission denied to COPY to a file
DETAIL: Only roles with privileges of the "pg_write_server_files" role may COPY to a file.
HINT: Anyone can COPY to stdout or from stdin. psql's \copy command also works for anyone.
スーパーユーザーでも、postgres の OS ユーザーが書けない場所には出せません。/root/item.csv を指定すると could not open file "/root/item.csv" for writing: Permission denied になりました。相対パスでは relative path not allowed for COPY to file になりました。
CSV に出力する
COPY item TO '/tmp/fsvpg/item.csv' WITH (FORMAT csv, HEADER);
COPY 3
id,name,price,memo,created
1,りんご,120.00,,2026-10-01
2,"みかん, 箱",980.50,"",
3,"say ""hi""",0.00,"改行
あり",2026-10-03
- NULL は何も無い(
,,)、空文字は""で出る ,や改行を含む値は"で囲まれ、値の中の"は""になるFORMAT csvを付けないと、タブ区切りで NULL が\Nの形式(text 形式)になる
条件を付けたいときは表の代わりに問い合わせを書きます。
\copy (SELECT id, name FROM item ORDER BY id) TO '/tmp/fsvpg/u2.csv' WITH (FORMAT csv, HEADER)
DELIMITER E'\t'… タブ区切りNULL 'NULL'… NULL を文字列NULLで出すFORCE_QUOTE *… 全部の値を"で囲む
\copy item TO '...' WITH CSV HEADER という古い書き方も通りました。\copy の行末に ; を付けても動きました。
CSV を取り込む
COPY item2 FROM '/tmp/fsvpg/item.csv' WITH (FORMAT csv, HEADER);
COPY 3
出力した CSV を取り込むと、NULL は NULL、"" は空文字に戻りました。HEADER を付け忘れると、見出し行を値として読んで invalid input syntax for type integer: "id" になります。
\copy で取り込むには、その表への INSERT 権限が要ります。権限が無いと permission denied for table item2 になります。
取り込みで止まる CSV
下の例はどれも取り込み後の件数が 0 でした。CONTEXT に CSV の行番号(見出しを1行目として数える)が出るので、そこを見て直します。
| エラー | 原因 | 対処 |
|---|---|---|
invalid input syntax for type numeric: "x" |
型に合わない値 | CSV を直す/ON_ERROR ignore |
extra data after last expected column |
列が多い行がある | CSV を直す |
missing data for column "memo" |
列が足りない行がある | CSV を直す |
duplicate key value violates unique constraint |
主キーの重複 | 一時表に入れてから INSERT ... ON CONFLICT |
invalid byte sequence for encoding "UTF8": 0x82 |
Shift_JIS の CSV | ENCODING 'SJIS' |
invalid input syntax for type integer: "51" |
先頭に BOM があり、見出し行が無い | BOM を外す |
column name mismatch in header line field 1 |
HEADER MATCH で、見出しの列名・順番が表と違う |
列リストを CSV の順に書く |
BOM 付きの CSV は、HEADER なら見出し行ごと読み飛ばされるので問題なく入りました。HEADER MATCH だと BOM が列名に付いて got "id", expected "id" になります。
CSV の列の順番が表と違うときは、列リストを書きます。
COPY item2 (name, id, price, memo, created) FROM '/tmp/fsvpg/order.csv' WITH (FORMAT csv, HEADER MATCH);
改行が CRLF の CSV はそのまま入りました。日付の 2026/10/01 と 10/02/2026 も、DateStyle が既定の ISO, MDY のまま読めました。
型の合わない行だけ飛ばす(PostgreSQL 17 以降)
COPY item2 FROM '/tmp/fsvpg/bad_type.csv' WITH (FORMAT csv, HEADER, ON_ERROR ignore, LOG_VERBOSITY verbose);
NOTICE: skipping row due to data type incompatibility at line 3 for column "price": "x"
NOTICE: 1 row was skipped due to data type incompatibility
COPY 2
LOG_VERBOSITY verbose を付けると、飛ばした行の番号と値が出ます。付けないと件数だけが出ます。PostgreSQL 18 では REJECT_LIMIT で飛ばす行数の上限も決められます(0 は指定できず、1 以上)。
列数の過不足と主キーの重複は、ON_ERROR ignore を付けても同じエラーで全体が止まりました。
関連: PostgreSQLの「duplicate key value violates unique constraint」 / PostgreSQLの「permission denied」 / MySQLでCSVを取り込む(LOAD DATA)