一昨日昨日の作業で、PostgreSQL 9.5の新機能IMPORT FOREIGN SCHEMAを使い、uDigのPostGISサンプルデータを一括して外部テーブルにできました(下の画像)。今日は結果の確認、外部テーブルをこちら側のローカルなテーブルにコピーする例、そして簡単なまとめ。
インポートした外部テーブルの確認
上のpgAdminの画像のとおり、5つの外部テーブルができました。見かけ上は普通のテーブルと同様にクエリでSELECTできます。↓ 今回、外部テーブルにPostGISのジオメトリ型があってこれはPostgreSQLネイティブではないですが、こちら側にもPostGISインストール済なのでOK。
SELECT * FROM bc_border LIMIT 100;
次にbc_borderというテーブルを例に、列・制約などの定義をpgAdmin上で比べてみました。↓ 画像は「外部サーバ上の実テーブル」「こちらの外部テーブル」の順。serialは普通のintegerになり、主キーや制約(NOT NULL以外)は外部テーブルになし。ドキュメントのCREATE FOREIGN TABLEの項(日本語9.4版)を見ると、NOT NULL, NULL, DEFAULTだけが外部テーブルで定義可能らしいです。
-- Remote Table
CREATE TABLE bc_border
(
    gid serial NOT NULL,
    border_id integer,
    the_geom geometry,
    CONSTRAINT bc_border_pkey PRIMARY KEY (gid),
    CONSTRAINT enforce_dims_the_geom CHECK (ndims(the_geom) = 2),
    CONSTRAINT enforce_geotype_the_geom CHECK (
        geometrytype(the_geom) = 'LINESTRING'::text OR the_geom IS NULL),
    CONSTRAINT enforce_srid_the_geom CHECK (srid(the_geom) = 4326)
)
WITH (
    OIDS=FALSE
);

-- Foreign Table
CREATE FOREIGN TABLE public.bc_border
(
    gid integer NOT NULL,
    border_id integer,
    the_geom public.geometry
)
SERVER udig
OPTIONS (schema_name 'public', table_name 'bc_border');
↓ psqlでのテーブル確認。表示内容は実質pgAdminと同様です。
psql -U postgres -p 5437 test_fdw

psql (9.4.1, サーバー 9.5alpha1)
注意: psql メジャーバージョン 9.4, サーバーバージョン 9.5.
         psql の機能の中で、動作しないものがあるかもしれません。
"help" でヘルプを表示します.

test_fdw=# \d bc_border
               外部テーブル "public.bc_border"
    列     |    型    |  修飾語  |       FDWオプション
-----------+----------+----------+---------------------------
 gid       | integer  | not null | (column_name 'gid')
 border_id | integer  |          | (column_name 'border_id')
 the_geom  | geometry |          | (column_name 'the_geom')
Server: udig
FDW Options: (schema_name 'public', table_name 'bc_border')
9.5 Alpha 1のドキュメントでCREATE FOREIGN TABLEの頁を見ると、新たにCHECK制約も定義できるような感じ。IMPORT FOREIGN SCHEMAとの関係は不明で、何か分かったら追記します。 もっとも、今回の外部サーバのPostGISはバージョンが古く、上で見られる「ジオメトリの次元・型・空間参照系をCHECK制約に入れる」のは旧式。PostGIS 2.0以降は型修飾子(Typmod)を使って簡単にできます。そもそも上のCHECK制約に使われている関数自体、現存しません。そういう意味では、今回は列制約が外部テーブルに出てなくて問題なし。
外部テーブルをローカルにコピー
FDW本来の意義(外部のデータをそのままローカルと同様に扱う)とはズレますが、uDigのサンプルDBに毎回クエリするのは遅いし先方にも負荷がかかるし、PostGISの新しいバージョンでデータを処理したいので、ローカルにコピーします。 似たことはすでに6月21日の記事でdblinkを使って行いました。その場合、各テーブルの列名・型の定義をクエリに入力するのが手間。一方、今回作った外部テーブルを経由するとそれが不要。具体的には
CREATE TABLE ローカルテーブル AS SELECT * FROM 外部テーブル;
この一行で済みます。下が実際の例。最初にコピー先のスキーマft_copyを作成し、続いてbc_borderというテーブルをコピー。この時、外部サーバからテーブル全行を取得するので時間がかかりますが(先方のレスポンスや回線速度も影響)、無事できました。
CREATE SCHEMA ft_copy;
CREATE TABLE ft_copy.bc_border AS SELECT * FROM bc_border;

Query returned successfully: 5199 rows affected, 15374 ms execution time.
↓ 同様に、残りのテーブル4つをコピーした様子。クエリの中で変えたのはテーブル名だけ。
CREATE TABLE ft_copy.bc_hospitals AS
SELECT * FROM bc_hospitals;

CREATE TABLE ft_copy.bc_municipality AS
SELECT * FROM bc_municipality;

CREATE TABLE ft_copy.bc_pubs AS
SELECT * FROM bc_pubs;

CREATE TABLE ft_copy.bc_voting_areas AS
SELECT * FROM bc_voting_areas;

Query returned successfully: 7986 rows affected, 43597 ms execution time.
↓ 終わった後のデータベース内。publicスキーマにある外部テーブルが、ft_copyスキーマに普通のテーブルとして一括コピーできました。列制約は未設定です(単純にSELECT結果が入力されただけなので)。
まとめ:列数の多いテーブル、テーブルの多いスキーマのコピーがすごい楽!
postgres_fdwインストールなど準備を除く、一連の手順は次のようになります。
CREATE SERVER [任意のサーバ名] FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '...', port '...', dbname '...');

CREATE USER MAPPING FOR [ローカルユーザ名]
    SERVER [上で設定したサーバ名]
    OPTIONS (user '...', password '...');

IMPORT FOREIGN SCHEMA [外部サーバのスキーマ名] LIMIT TO (
    テーブル1,
    テーブル2,
    ...
    -- スキーマ内を一括取り込む場合は LIMIT TO 不要
) FROM SERVER [設定したサーバ名] INTO [こちらのスキーマ名];

-- 以上で外部テーブル化できた
-- 普通のローカルなテーブルにコピーしたければ、次を付加

CREATE TABLE [ローカルテーブル1] AS SELECT * FROM [外部テーブル1];
CREATE TABLE [ローカルテーブル2] AS SELECT * FROM [外部テーブル2];
...
このように、各テーブルの列構成を確認・記述する手間なくできました。 最後にコピーしたテーブルbc_voting_areasは ↓ 14列あってデータ型もいろいろ。普通ならpg_dumpを使う所ですが、今回の外部サーバでは失敗(詳細:6月21日の記事)。そんな時、IMPORT FOREIGN SCHEMAによる外部テーブル経由でのコピーが代替手段になります。 コピーしたいテーブルが一つでも ↓ のように使用可。後は、前項のように「CREATE TABLE ... AS SELECT * FROM ...」の一文でローカルテーブルにすればOK。
IMPORT FOREIGN SCHEMA [外部サーバのスキーマ名] LIMIT TO (
    テーブル1
) FROM SERVER [外部サーバ名] INTO こちらのスキーマ名;
テーブルが多いスキーマの場合、PL/pgSQLで動的SQLを作って1テーブルずつループする ↓ といいかも。まだ試してないので思い付き段階ですが。pg_classというシステムカタログのrelkind列がfなら外部テーブルらしく、これをWHERE句に使って外部テーブル群を取得し、1行ずつ「CREATE TABLE ... AS SELECT * FROM ...」に代入して動的SQLにするという。
DO $$
DECLARE tbn text;
BEGIN
    FOR tbn IN
        SELECT relname FROM pg_class WHERE relkind = 'f'
    LOOP
        EXECUTE 'CREATE TABLE copy_' || tbn ||
            ' AS SELECT * FROM ' || tbn;
    END LOOP;
END;
$$;
pg_dumpと比較して、今回の「外部テーブル経由でローカルにコピー」の短所は別途インデクスや列制約を付ける必要があること。でも前々項に書いたPostGISのジオメトリ制約のように、外部サーバでの定義をそのままローカルに持ってこれない時もあり、結局は個別に検討する場合が多いので、実質問題なさそう。 とかくGISデータでは、一つのスキーマに「都道府県別シェープファイルからインポートした47個×年数のテーブル」があるとか、元データに何十列もあったりして、データ移動/コピーが面倒になりがち。それが今回のIMPORT FOREIGN SCHEMAですごく楽になり、もちろんFDW本来の「外部データをローカルテーブルのように使う」時も有益。自分には間違いなくPostgreSQL 9.5の重要な新機能の一つです。