SnowflakeではCOPY INTO (場所)を使って、テーブルやビューの中身を外部ストレージに書き出す機能があります。例えばtable_aの内容を全てアンロードしようと思ったら、次のように書きます。
COPY INTO @外部ステージ名/ファイル名.csv
FROM (SELECT * FROM table_a)
FILE_FORMAT = (TYPE = CSV)
COPY INTOはデフォルトではウェアハウスのサイズごとに定義された同時実行クエリ数だけ並列で動作します(X-SMALLの時は8)。その結果、生成されるCSVファイルも複数できあがります。
課題1「ORDER BYできない」
「出力されるCSVがID順に並んでいて欲しい」という、至極真っ当な要望がありました。このように書いてみるとどうなるでしょうか?
COPY INTO @外部ステージ名/ファイル名.csv
FROM (SELECT * FROM table_a ORDER BY id)
FILE_FORMAT = (TYPE = CSV)
これは少なくとも二つ問題があります。
一つ目の問題は、複数ファイルが出来上がりますが、ファイル名からはファイル単位での順序がわかりません。具体的にはアンロードされたファイルはファイル名_サフィクス.csvとなりますが、サフィックスに命名の規則はありません。
二つ目の問題として、個別のファイルの中身を見ても、その中でORDER BYにより指定された順序に並んでいるかどうかは、いろいろ調べた結果によると保証されないということがわかりました。
課題2「並び替えを有効にすると遅い」
アンロードで並び順を保証する仕組みは、存在します。それはSINGLE = TRUEを指定して、単一のファイルを生成する方式で、アンロード処理を実行するというものです。
COPY INTO @外部ステージ名/ファイル名.csv
FROM (SELECT * FROM table_a ORDER BY id)
FILE_FORMAT = (TYPE = CSV)
SINGLE = TRUE
この結果、ファイル名.csvという単一ファイルが出来上がります。そして中身はORDER BYで指定された通りの並び順になっています。
しかしこれは、単一のクエリで実行されるため、とても遅いです。例えば同時実行クエリ数が8のウェアハウスで実行した場合、基本的なアンロードに比べて8倍遅くなります。
解決:非同期子ジョブを使う
解決のコンセプトとしては「SINGLE=TRUEと指定したCOPY INTOコマンドを、複数並列に実行する」というものです。そのための機能がASYNC/AWAITとして提供されています。
単一コマンドではできなくなるので、ここでは無名プロシージャとして処理を作成します。
BEGIN
LET divisions INTEGER := 8;
LET size INTEGER := (SELECT CEIL(COUNT(*)/8) FROM table_a);
ASYNC (
COPY INTO @外部ステージ名/ファイル名-0.csv
FROM (SELECT * FROM table_a ORDER BY id LIMIT size OFFSET 0 * size)
FILE_FORMAT = (TYPE = CSV)
);
ASYNC (
COPY INTO @外部ステージ名/ファイル名-1.csv
FROM (SELECT * FROM table_a ORDER BY id LIMIT size OFFSET 1 * size)
FILE_FORMAT = (TYPE = CSV)
);
-- 以下2から7まで同様に記載する。
-- 注:FOR文を使って書くことも可能です。
-- ただしCOPY INTO (場所)の場所にはバインド変数を使用することはできません。
-- そのためASYNCに指定するべきSQLを文字列で作成して、EXECUTE IMMEDIATEを使って動的SQLとして実行してください
AWAIT ALL;
END
上記では全部で8個のSQLが発行されます。しかしASYNCが指定されているので、並列に動作します。
AWAIT ALLでは、上記の全てのSQLが終了するまで動作を待ちます。
この結果、ファイル名-0.csvからファイル名-7.csvまで8つのCSVファイルが出来上がります。これらのファイルの中はSINGLE=TRUEとして出力されているのでソート順は保証されています。またファイル名もプログラム内で独自に作ったので、連番であることが保証されています。
そのため、ファイル名-0.csvからファイル名-7.csvを順に結合することで、パフォーマンスを落とさずに並列に、かつ順序が維持されたCSVファイルを入手することができました。