ODBCはデータソース設定が面倒、pgOleDBは5432ポート以外のサーバに接続できないので、psql経由で簡易にデータを取り込むマクロ付Excelファイルを作ってみました(psql_template.xlsm.zip)。
Contents
- 実行環境、動機、Excelファイル
- 使用例(1)
- 使用例(2)
- 注意点
実行環境、動機、Excelファイル
Windows7 32bit + Excel 2007 + psql 9.4.1(PostgreSQL Portable 9.4.1に付属)で動作確認しました。
自分のためにExcelを使うことは基本ないのですが、人から「データをExcelでくれ」と言われた時、サクッと済ませたい。DBがいろいろあるので、DBごとにデータソースを設定するODBCは面倒。pgOleDBは、前のブログで書いたとおり標準ポート5432以外のサーバに接続できないのが致命的。
で、結局pgAdminで表示したのをコピペするか、COPYコマンドでTSV出力してExcelで開いて保存し直すかの二択になってました。それよりはExcel上で完結する方が楽だなと思い、データベースやポートによらずデータを持ってこれるpsqlをVBAから起動することに。
作成したExcelファイルはこちら(psql_template.xlsm.zip。冒頭の再掲)。ZIPの中にxlsmファイル一つが入ってます。当然ながら使う際はマクロを有効に ↓ します。画像の下がマクロの中身(プロシジャ一つ)で、エラー処理なしの超簡易(手抜き)版。WScript.ShellのExecメソッドでpsqlコマンドを実行し、返ってきた標準出力を新規ワークシートに貼り付けるだけです。
Sub psql()
Dim cmd, oExec, ws, nrow, cols
cmd = Range("C2").Value & _
" -h " & Range("C3").Value & _
" -p " & Range("C4").Value & _
" -d " & Range("C5").Value & _
" -U " & Range("C6").Value & _
" -c """ & Range("C7").Value & """ -A"
Set oExec = CreateObject("Wscript.Shell").Exec(cmd)
Set ws = Worksheets.Add
nrow = 1
While Not oExec.StdOut.AtEndOfStream
cols = Split(oExec.StdOut.ReadLine, "|")
For i = LBound(cols) To UBound(cols)
ws.Cells(nrow, i + 1).Value = cols(i)
Next i
nrow = nrow + 1
Wend
Set oExec = Nothing
End Sub
psqlは、パラメータ-cに続けてSQLを渡すことが可能。またパラメータ-Aを付けると、データの区切りが|だけに変わる(デフォルトは、|の前後に半角空白を付けて整形)。こうして得た結果の一行ずつを|で区切って列と見なし、新規シートに貼り付けます。だからデータ中に|があると結果が変ですが、そういうケースは自分の場合稀なので考慮しませんでした。
使用例(1)
最初にpsqlの場所を確認します。PostgreSQLインストール済みPCならそのプログラムフォルダの中。そうでない場合、先ほど実行環境の所に書いたPostgreSQL Portableをダウンロードし、適当なフォルダにインストールすれば(実際行われるのはファイルの解凍のみ)、その中にpsqlがあります。
Excelファイルはシート一つで、マクロ実行は、プロシジャを割り当てたボタンから(下の画像参照)。ボタンがクリックされると、セルC2~C7に入力した内容から実行コマンドが作られるという簡単な仕組み。各セルの項目は下のとおり。
C2…psqlのフルパス(半角空白を含む場合、引用符が必要)
C3… ホスト名またはIPアドレス
C4… ポート番号
C5… データベース名
C6… ユーザ名
C7…SQL
例えばSQL欄にこんな ↓ クエリを入力して実行すると一瞬コマンドプロンプトが出てpsqlを起動し、元シートの手前に新規シートができ、左上(A1)を起点にデータが貼り付けられます。
SELECT * FROM pg_tables LIMIT 25
データ末尾に結果行数が付きます。ない方が使いやすいですが(新シートがすなわち一つの表になる)、psqlでこれを消すにはパラメータ-tを指定するしかなく、そうすると列名も非表示になるので今回はそのまま。
実行に関係するセルはC2~C7だけ。他の場所にSQLの控えやメモなど、自由に置けます。
使用例(2)
↓ 乱数を1万個出力する例。今回のVBAは単純に1セルずつ入力する遅い方法だけど、この程度だとほぼ一瞬で終了(Core i5-4300MのノートPC)。本当は、全体を配列に入れて1回で貼り付ける方が効率的です。
SELECT random() FROM generate_series(1, 10000)
シート入力時に特段の処理をしてないので、01のような先頭ゼロ埋め数字は、Excelが自動的に普通の数値に変えます(下のクエリ1行目、画像の左)。もしゼロ埋めなどフォーマットを維持したければ、Excelに「これは文字列だよ」と知らせる先頭シングルクォーテーションを、クエリ時点から入れておくとか(クエリ2行目、画像の右)。SQLなのでシングルクォーテーションが2重になって見にくいけど。
SELECT to_char(gs, 'FM00') FROM generate_series(1, 50) gs
SELECT to_char(gs, '''FM00') FROM generate_series(1, 50) gs
psqlの-cパラメータは複文(セミコロンで区切った複数のSQL)を許容するので、こんな風に ↓DO文の中で指定秒数を待って結果を取得することが可能。というか、一瞬出るコマンドプロンプトのスクリーンショットを撮るためにやってみました。
DO $$ BEGIN PERFORM pg_sleep(1); END $$; SELECT now()
注意点
今回使ったWScript.ShellのExecメソッドには、いわゆる4KB問題があります。標準出力と標準エラーのバッファが4KBで、それを超えるとハングアップするという。今回、標準出力を1行ずつReadLineメソッドで処理してるので普通は大丈夫だと思いますが(試しに結果が10万行×3列になるクエリ ↓ を実行したら、数秒かかったけど正常終了)、1行のサイズまたはエラー出力が4KB超だと駄目。
SELECT random(), random(), random() FROM generate_series(1, 100000)
また、誤ったクエリとか、クエリに日本語が入っていると ↓psqlから結果が返ってこず、新しい空のシートが出来るだけになります。日本語の件は、どこで引っかかっているのか未調査。
SELECT 'あいうえお' -- error
↓ 余り意味ないですが、英数字だけのコメントはクエリに付加して実行可能。
SELECT random() -- comments
先ほど乱数1万個のクエリで書いたとおり、単純に1セルずつ入力するのでデータ量が多いと遅いです。またpsql実行時にパスワード入力が必要だと、↓ のようにコマンドブロンプトが入力待ちになりますが、なぜか入力文字列がそのまま表示されます。普通にpsqlを使う時は非表示なのに、謎。