PostgreSQL 10 min read

PostgreSQLでCSVを入出力する ― COPYと\copyの違い、取り込みが止まる行の直し方

※この記事にはアフィリエイト広告(プロモーション)が含まれます。

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)

PostgreSQL をもっと深く知るための本

PostgreSQL実践入門 ──アーキテクチャ、運用監視、性能改善

技術評論社・2026年2月

アーキテクチャから運用監視・性能改善までを扱う。付録に PostgreSQL 18 の新機能と設定項目の差分がある。

Amazonで見る

[改訂3版]内部構造から学ぶPostgreSQL ―設計・運用計画の鉄則

技術評論社・2022年11月・PostgreSQL 14 ベース

内部構造の理解から、設計と運用計画の立て方を学ぶ。

Amazonで見る

Amazonのアソシエイトとして、FIRE SE は適格販売により収入を得ています。