第4章 パフォーマンス最適化・クエリ・変換 / 想定学習時間:30〜40分 / 最終確認:2026年8月

4-6. 非構造化データとウィンドウ関数・集約

🎯 この節の学習目標

1. 非構造化データ:ステージとディレクトリテーブル

画像・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)が必要です。

2. 3種類のファイルURL関数(比較必須)

ステージ上のファイルへアクセスするURLを生成する関数は3つあり、「URLの寿命」と「アクセス時に必要な認証・権限」が異なります。この違いは試験の定番論点です。

BUILD_SCOPED_FILE_URLBUILD_STAGE_FILE_URLGET_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択の判別ができれば十分です。

ステージ上のファイル画像・PDF などの非構造化データ(DIRECTORY = TRUE)
DIRECTORY(@stage) でメタデータを一覧
ディレクトリテーブルrelative_path・size・last_modified・file_url
用途に応じてURL関数を選択
BUILD_SCOPED_FILE_URL一時的・権限連動
BUILD_STAGE_FILE_URL永続・ステージ権限が必要
GET_PRESIGNED_URL期限付き・認証不要で共有可
アプリ・BI・外部共有で利用URL経由でファイル本体へアクセス

図:非構造化ファイルの管理からURL共有までの流れ

3. ウィンドウ関数:OVER句の基本

ここからはクエリ・変換の道具に移ります。ウィンドウ関数は、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 ... でウィンドウ枠を指定すると移動集計(移動平均・累計)になります。

4. QUALIFY句:ウィンドウ関数の結果でフィルタする

「グループごとに最新の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の特徴的な構文で、「ウィンドウ関数の結果で絞り込むにはどの句を使うか」という形で問われます。

5. 集約:GROUP BYとその拡張

基本の 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時間だけ共有したい。最も適切な関数はどれか。

  1. BUILD_SCOPED_FILE_URL
  2. BUILD_STAGE_FILE_URL
  3. GET_PRESIGNED_URL
  4. DIRECTORY
解答と解説を見る

正解:C

GET_PRESIGNED_URLは有効期限を指定できる署名済みURLを生成し、Snowflakeへの認証なしでアクセスできるため、外部共有に適しています。AとBのURLはいずれもアクセス時にSnowflakeの認証(さらにBはステージ権限)が必要なため、Snowflakeユーザーでない相手には使えません。Dのディレクトリテーブルはファイルのメタデータを一覧する仕組みで、共有用URLを生成する関数ではありません。

問2. ディレクトリテーブルに関する説明として正しいものはどれか。

  1. ステージ作成時に DIRECTORY = (ENABLE = TRUE) を指定すると有効化でき、ファイルのメタデータをSQLで照会できる
  2. 非構造化ファイルの中身(バイナリデータ)を行として格納するテーブルである
  3. 外部ステージでは使用できず、内部ステージ専用の機能である
  4. 有効化すると、ファイルの追加・削除が即時かつ自動的に反映され、リフレッシュの概念はない
解答と解説を見る

正解:A

ディレクトリテーブルはステージのオプションとして有効化し、DIRECTORY(@stage) でrelative_path・size・last_modifiedなどのメタデータを照会できます。Bは誤りで、返るのはメタデータであり中身ではありません。Cは誤りで、内部・外部どちらのステージでも利用できます。Dは誤りで、内容を最新化するにはリフレッシュ(自動リフレッシュ設定または ALTER STAGE ... REFRESH)が必要です。

問3. 顧客ごとに最新の注文1件だけを返すクエリを、サブクエリを使わずに書きたい。Snowflakeで使うべき句はどれか。

  1. WHERE ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1
  2. HAVING ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1
  3. QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1
  4. GROUP BY customer_id ORDER BY order_date DESC LIMIT 1
解答と解説を見る

正解:C

ウィンドウ関数の結果でフィルタするのはQUALIFY句の役割で、Snowflakeの特徴的な構文です。AのWHERE句ではウィンドウ関数を使えません(評価順序がウィンドウ関数より先のため)。BのHAVINGはGROUP BYのグループに対するフィルタで、やはりウィンドウ関数は使えません。DのLIMITはクエリ全体の行数を制限するだけで、「顧客ごとに1件」にはなりません。

問4. ORDER BY score DESC で並べた値が 100, 90, 90, 80 のとき、ウィンドウ関数の返す順位として正しい組み合わせはどれか。

  1. RANK: 1,2,2,3 / DENSE_RANK: 1,2,2,4 / ROW_NUMBER: 1,2,3,4
  2. RANK: 1,2,2,4 / DENSE_RANK: 1,2,2,3 / ROW_NUMBER: 1,2,3,4
  3. RANK: 1,2,3,4 / DENSE_RANK: 1,2,2,3 / ROW_NUMBER: 1,2,2,4
  4. RANK: 1,2,2,3 / DENSE_RANK: 1,2,2,3 / ROW_NUMBER: 1,2,2,4
解答と解説を見る

正解: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)があり、連番になるという性質に反します。