PostgreSQLでテーブルをCSVダンプする

DBのデータを自分で作成したプログラムで更新した時に、どうやって検証をするかという内容です。
PostgreSQLにはCOPY文でCSVに出力することが可能なので、出力したCSVを更新前後でdiffりました。

まずはDBの中身はこんな感じ。testという名前のDBに↓の様なデータを入れておきます。

test=# SELECT * FROM test_table;
 id |   name    |    tel     
----+-----------+------------
  1 | yamada    | 012345678
  2 | takahashi | 123456789
  3 | suzuki    | 234567890
  1 | takeda    | 3456789012
(4 rows)

コマンドは、以下、1行だけ。

psql <DB名> -c "COPY <テーブル名> TO '<ファイル名>' CSV
---------------------------------------------------
【例】
[root@Melchior ~]# psql test -c "COPY test_table TO '/tmp/hoge.csv' CSV"
COPY 4
[root@Melchior ~]# cat /tmp/hoge.csv 
1,yamada,012345678
2,takahashi,123456789
3,suzuki,234567890
4,takeda,3456789012

条件を指定することも出来ます。

[root@Melchior ~]# psql test -c "COPY (SELECT * FROM test_table WHERE id = 1) TO '/tmp/hoge.csv' CSV"
COPY 1
[root@Melchior ~]# cat /tmp/hoge.csv
1,yamada,012345678

複数のテーブルがある時に、テーブルによって条件を変えてCSV化する必要があったので簡単なシェルスクリプトを作りました。仕様はこんな感じ。

  • 引数に、DB名、テーブルのリスト、出力先のディレクトリを指定。
  • テーブルのリストには、テーブル名、WHERE句以降の条件がパイプ区切りで記載。
  • 出力するファイル名は<テーブル名>.csvとする。
#!/bin/sh

usage() {
    MESSAGE=$1
    echo "Usage: $0 <db name> <db list> <dump dir>"
    echo "       ** $MESSAGE"
    exit 1
}

DB_NAME=$1
DB_LIST=$2
DUMP_DIR=$3
CURDIR=`pwd`
DUMP_DIR="$CURDIR/$DUMP_DIR"

if [ -z $DB_NAME ]; then
    usage "DB NAME is empty!"
fi
if [ -z $DB_LIST  ]; then
    usage "DB LIST is empty!"
fi
if [ -z $DUMP_DIR ]; then
    usage "DUMP DIR is empty!"
fi
if [ ! -f $DB_LIST ]; then
    usage "DB LIST is not file!"
fi
if [ ! -d $DUMP_DIR ]; then
    usage "DUMP DIR is not directory!"
fi

cat $DB_LIST | while read line
do
    TABLENAME=`echo $line | awk -F'|' '{print $1}'`
    CONDITION=`echo $line | awk -F'|' '{print $2}'`
    if [ ! -z "$CONDITION" ]; then
	psql "$DB_NAME" -c "COPY (SELECT * FROM $TABLENAME WHERE $CONDITION) TO '$DUMP_DIR/$TABLENAME.csv' CSV"
    else
	psql "$DB_NAME" -c "COPY $TABLENAME TO '$DUMP_DIR/$TABLENAME.csv' CSV"
    fi
    if [ $? -ne 0 ]; then
	echo "Error occurred"
	exit 1
    fi
done

dblist.txtの中身。

test_table|id = 1
test_table2

DBの中身。

test=# select * from test_table;
 id |   name    |    tel     
----+-----------+------------
  1 | yamada    | 012345678
  2 | takahashi | 123456789
  3 | suzuki    | 234567890
  4 | takeda    | 3456789012
(4 行)

test=# select * from test_table2;
 id  | name | tel 
-----+------+-----
 100 | aaa  | 000
 101 | bbb  | 111
 102 | ccc  | 222
(3 行)

以下、実行例。

[root@Melchior ~]# ./db2csv.sh test dblist.txt tmp/
COPY 1
COPY 3
[root@Melchior ~]# 
[root@Melchior ~]# 
[root@Melchior ~]# ll tmp/
total 4
-rw-r--r--  1 pgsql  wheel  19  5 17 01:12 test_table.csv
-rw-r--r--  1 pgsql  wheel  36  5 17 01:12 test_table2.csv
[root@Melchior ~]# 
[root@Melchior ~]# more tmp/*.csv
1,yamada,012345678
...skipping...
100,aaa,000
101,bbb,111
102,ccc,222

※作ったコードそのままではありません。

注意

  • ファイルのパスは絶対パスでないとダメ。
  • ファイルのパスはシングルクオートで囲まないとダメっぽい。特にシェルスクリプトを作る時は注意。
  • PostgreSQLのユーザで実行されるため、出力先がPostgreSQLのユーザで書き込めるところでないとダメ。↑の場合、rootでmkdirすると、↓みたいに怒られる。
ERROR:  could not open file "/root/tmp/test_table.csv" for writing: Permission denied

今回のシェルスクリプトに関わらず、シェルスクリプトの定石みたいなのは覚えておくと便利だと思います。
(↑のcat してwhileでreadを回すみたいなところ)