第4章 パフォーマンス最適化・クエリ・変換(Performance & Transformation, 21%)
🎯 この節の学習目標
画像・PDF・音声のような非構造化データは、テーブルの行としてではなく、ステージ上のファイルとして扱います。Snowflake はステージにディレクトリテーブル(directory table)という仕組みを用意しており、有効化するとステージ内のファイル一覧をテーブルのようにSQLで照会できます。
-- ディレクトリテーブルを有効にしてステージを作成
CREATE STAGE doc_stage
DIRECTORY = (ENABLE = TRUE)
ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');
-- ステージ上のファイルのメタデータを一覧する
SELECT relative_path, size, last_modified, file_url
FROM DIRECTORY(@doc_stage);
ディレクトリテーブルが返すのはファイルのメタデータ(相対パス・サイズ・更新日時・ファイルURLなど)であり、ファイルの中身そのものではありません。中身へのアクセスは、次のURL関数で得たURLを通じて行います。なお、ステージへのファイル追加・削除を反映するには、ディレクトリテーブルのリフレッシュ(自動または ALTER STAGE ... REFRESH)が必要です。
ステージ上のファイルへアクセスするURLを生成する関数は3つあり、「URLの寿命」と「アクセス時に必要な認証・権限」が異なります。この違いは試験の定番論点です。
| BUILD_SCOPED_FILE_URL | BUILD_STAGE_FILE_URL | GET_PRESIGNED_URL | |
|---|---|---|---|
| URLの性質 | スコープ付き(一時的)なURL | 永続的なURL(ファイルが同じ場所にある限り有効) | 有効期限付きの署名済みURL(期限は引数で指定、既定3600秒) |
| アクセスに必要なもの | Snowflakeへの認証。URLを発行したクエリ結果を受け取った人が使える(エンコードされた権限に連動) | Snowflakeへの認証+そのステージへの権限(USAGE/READ) | 認証不要。URLを知っていれば誰でも期限内はアクセス可能 |
| 典型用途 | アプリ経由で一時的にファイルを見せる(権限を厳密に保ちたい場合) | 権限を持つユーザー向けの恒久的なリンク(社内カタログなど) | 外部の相手への期限付き共有、BIツールへの画像埋め込みなど |
📝 試験のポイント
「Snowflakeにログインできない外部の相手に、期限付きでファイルを共有したい」→ GET_PRESIGNED_URL。「ステージ権限を持つ人だけが使える永続URL」→ BUILD_STAGE_FILE_URL。「一時的・発行時の文脈に紐づくURL」→ BUILD_SCOPED_FILE_URL。この3択の判別ができれば十分です。
図:非構造化ファイルの管理からURL共有までの流れ
ここからはクエリ・変換の道具に移ります。ウィンドウ関数は、GROUP BY のように行をまとめてしまわずに、各行を残したまま「行の集合(ウィンドウ)」に対する計算結果を各行に付与する関数です。基本構文は次のとおりです。
関数() OVER (
PARTITION BY 区切りの列 -- 区切りごとに独立して計算(省略可)
ORDER BY 並び順の列 -- ウィンドウ内の順序(関数によって必須)
)
| 関数 | 働き | 順位の付き方の例(値: 10, 20, 20, 30) |
|---|---|---|
| ROW_NUMBER() | ウィンドウ内の連番(同値でも重複しない) | 1, 2, 3, 4 |
| RANK() | 同値は同順位、次の順位は飛ぶ | 1, 2, 2, 4 |
| DENSE_RANK() | 同値は同順位、次の順位は飛ばない | 1, 2, 2, 3 |
| LAG(col) / LEAD(col) | 前の行/次の行の値を参照する(前期比較などに使う) | — |
💡 具体例:前月比と移動平均
SELECT
month,
amount,
LAG(amount) OVER (ORDER BY month) AS prev_amount, -- 前月の値
amount - LAG(amount) OVER (ORDER BY month) AS diff, -- 前月差
AVG(amount) OVER (
ORDER BY month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3m -- 3か月移動平均
FROM monthly_sales;
SUM や AVG などの集計関数も、OVER句を付ければウィンドウ関数として使えます。ROWS BETWEEN ... AND ... でウィンドウ枠を指定すると移動集計(移動平均・累計)になります。
「グループごとに最新の1行だけ欲しい」はデータ変換の定番要求ですが、WHERE句ではウィンドウ関数を使えません(WHEREはウィンドウ関数より先に評価されるため)。標準SQLではサブクエリで囲む必要がありますが、SnowflakeにはQUALIFY句があり、1段で書けます。
-- 顧客ごとに最新の注文1件だけを取り出す
SELECT customer_id, order_id, order_date
FROM orders
QUALIFY ROW_NUMBER() OVER (
PARTITION BY customer_id ORDER BY order_date DESC
) = 1;
評価順序のイメージは「WHERE が行に対するフィルタ、HAVING がグループに対するフィルタ、QUALIFY がウィンドウ関数の結果に対するフィルタ」です。QUALIFY はSnowflakeの特徴的な構文で、「ウィンドウ関数の結果で絞り込むにはどの句を使うか」という形で問われます。
基本の GROUP BY と、グループ化後の絞り込みである HAVING は前提知識として、Snowflake は小計・総計を一度に計算する拡張構文もサポートしています。詳細な構文を暗記する必要はなく、何が増えるかを押さえておけば十分です。
| 構文 | 生成されるグループ | 例:GROUP BY 〜 (region, product) |
|---|---|---|
| ROLLUP | 階層的な小計+総計 | (region, product)、(region)小計、()総計 |
| CUBE | すべての組み合わせの小計+総計 | (region, product)、(region)、(product)、() |
| GROUPING SETS | 指定した組み合わせのみ | 指定したセット(例:(region) と (product) のみ) |
ROLLUP は「地域→商品」のような階層の小計、CUBE は全方向のクロス集計、GROUPING SETS は必要な集計単位だけを明示する、と整理しておきましょう。
✅ この節のまとめ
問1. Snowflakeにアクセスできない外部の取引先に、ステージ上のPDFファイルを48時間だけ共有したい。最も適切な関数はどれか。
正解:C
GET_PRESIGNED_URLは有効期限を指定できる署名済みURLを生成し、Snowflakeへの認証なしでアクセスできるため、外部共有に適しています。AとBのURLはいずれもアクセス時にSnowflakeの認証(さらにBはステージ権限)が必要なため、Snowflakeユーザーでない相手には使えません。Dのディレクトリテーブルはファイルのメタデータを一覧する仕組みで、共有用URLを生成する関数ではありません。
問2. ディレクトリテーブルに関する説明として正しいものはどれか。
正解:A
ディレクトリテーブルはステージのオプションとして有効化し、DIRECTORY(@stage) でrelative_path・size・last_modifiedなどのメタデータを照会できます。Bは誤りで、返るのはメタデータであり中身ではありません。Cは誤りで、内部・外部どちらのステージでも利用できます。Dは誤りで、内容を最新化するにはリフレッシュ(自動リフレッシュ設定または ALTER STAGE ... REFRESH)が必要です。
問3. 顧客ごとに最新の注文1件だけを返すクエリを、サブクエリを使わずに書きたい。Snowflakeで使うべき句はどれか。
正解:C
ウィンドウ関数の結果でフィルタするのはQUALIFY句の役割で、Snowflakeの特徴的な構文です。AのWHERE句ではウィンドウ関数を使えません(評価順序がウィンドウ関数より先のため)。BのHAVINGはGROUP BYのグループに対するフィルタで、やはりウィンドウ関数は使えません。DのLIMITはクエリ全体の行数を制限するだけで、「顧客ごとに1件」にはなりません。
問4. ORDER BY score DESC で並べた値が 100, 90, 90, 80 のとき、ウィンドウ関数の返す順位として正しい組み合わせはどれか。
正解:B
RANKは同値(90が2つ)に同順位2を与え、次の値は人数分飛んで4位になります(1,2,2,4)。DENSE_RANKは順位が飛ばず3位になります(1,2,2,3)。ROW_NUMBERは同値でも重複せず連番です(1,2,3,4)。AはRANKとDENSE_RANKの説明が入れ替わっています。CとDはROW_NUMBERに重複(2,2)があり、連番になるという性質に反します。