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

4-7. SQL機能(MERGE・サンプリング・推定関数・UDF・ストアドプロシージャ)

🎯 この節の学習目標

1. MERGE・INSERT OVERWRITE・マルチテーブルINSERT

日々の変換処理でよく使う3つの書き込み構文を押さえます。まず MERGE は、ソースとターゲットをキーで突き合わせ、一致したら更新(または削除)、一致しなければ挿入を1文で行う、いわゆる UPSERT の構文です。増分データの取り込み(3章のストリーム処理の後段など)の定番です。

MERGE INTO customers t
USING staged_updates s
  ON t.customer_id = s.customer_id
WHEN MATCHED THEN
  UPDATE SET t.email = s.email, t.updated_at = s.updated_at
WHEN NOT MATCHED THEN
  INSERT (customer_id, email, updated_at)
  VALUES (s.customer_id, s.email, s.updated_at);

INSERT OVERWRITE は、挿入の前にターゲットテーブルを空にしてから(TRUNCATE相当)書き込みます。「毎回全量を作り直す」集計テーブルの洗い替えに使い、削除と挿入が単一トランザクションで行われます。また Snowflake はマルチテーブルINSERT(INSERT ALL / INSERT FIRST)をサポートしており、1つのソースクエリの結果を条件に応じて複数のテーブルへ振り分けて挿入できます。

-- 条件に応じて2つのテーブルへ振り分ける(ALL: 合致する全WHENに挿入)
INSERT ALL
  WHEN amount >= 10000 THEN INTO large_orders
  WHEN amount <  10000 THEN INTO small_orders
SELECT * FROM staged_orders;

2. サンプリング:SAMPLE / TABLESAMPLE

巨大テーブルの傾向を素早く掴みたいとき、SAMPLE(TABLESAMPLE と同義)で一部の行だけを読み取れます。方式が2つあり、その違いが問われます。

BERNOULLI(別名 ROW)SYSTEM(別名 BLOCK)
抽出単位行ごとに指定確率で選ぶブロック(マイクロパーティション)ごとに選ぶ
精度統計的に偏りが少ない(行単位のランダム性)ブロック内の行がまとめて選ばれるため偏りが出やすい
速度全行を評価するため相対的に遅いブロック単位でスキップできるため大テーブルで高速
既定方式を省略した場合は BERNOULLI

💡 具体例:10%サンプルと再現可能なサンプル

-- 行単位で約10%を抽出(既定は BERNOULLI)
SELECT * FROM sales SAMPLE (10);

-- ブロック単位で約10%を抽出(大テーブルで高速)
SELECT * FROM sales SAMPLE SYSTEM (10);

-- シードを固定すると同じサンプルを再現できる
SELECT * FROM sales SAMPLE SYSTEM (10) REPEATABLE (42);

REPEATABLE(またはSEED)でシード値を固定すると、テーブルが変更されない限り同じ行の集合が返り、検証の再現性を確保できます。

3. 推定関数:高速な近似集計

「ユニークユーザー数」のようなカーディナリティ(個別値数)の厳密な計算は、大テーブルではメモリと時間を大量に消費します。Snowflake は HyperLogLog アルゴリズムに基づく APPROX_COUNT_DISTINCT(HLL とも書けます)を提供しており、誤差数%程度の近似値を、桁違いに少ないリソースで高速に返します。

-- 厳密だが重い
SELECT COUNT(DISTINCT user_id) FROM access_log;

-- 近似だが高速・省メモリ(HyperLogLog)
SELECT APPROX_COUNT_DISTINCT(user_id) FROM access_log;

同系統の関数として、上位頻出値を推定する APPROX_TOP_K、パーセンタイルを推定する APPROX_PERCENTILE などがあります。ダッシュボードの傾向把握など「厳密さより速度」が求められる場面で使い、課金や会計など厳密さが必要な集計には使わない、という使い分けが論点です。

4. UDF:ユーザー定義関数

UDF(User-Defined Function)は、組み込み関数と同じようにクエリの中で呼び出せる自作関数です。言語は SQL・JavaScript・Python・Java・Scala から選べます。戻り値の形で2種類に分かれます。

💡 具体例:SQLのスカラーUDF

CREATE FUNCTION tax_included(price NUMBER)
RETURNS NUMBER
AS
$$
  price * 1.1
$$;

SELECT item, tax_included(price) FROM order_items;

また、セキュアUDF(SECURE FUNCTION)として作成すると、関数の定義(ロジック)が権限のないユーザーから見えなくなり、オプティマイザによる内部情報の露出も防がれます。データ共有で関数を提供する場合などに使いますが、最適化の一部が抑制されるため性能が低下することがあります。

5. ストアドプロシージャとowner's rights / caller's rights

ストアドプロシージャは、CALL文で独立して実行する手続きです。Snowflake Scripting(SQL)・JavaScript・Python などで書け、内部で複数のSQL文を発行し、条件分岐・ループ・エラー処理といった手続きロジックを実装できます。DDL/DML の実行を伴う管理タスクや、タスク(3章)から呼び出す定期バッチの本体としてよく使われます。

重要なのが実行権限のモデルです。プロシージャは作成時に、どちらの権限で動くかが決まります。

Owner's rights(所有者の権限)Caller's rights(呼び出し元の権限)
実行時の権限プロシージャの所有者の権限で実行されるCALLしたユーザー(セッション)の権限で実行される
既定かどうか既定(EXECUTE AS OWNER)EXECUTE AS CALLER を明示して作成
典型用途呼び出し元に直接付与したくない操作を、手順を限定して代行させる(例:特定テーブルへの書き込みだけ許す)呼び出し元のセッション状態(現在のロール・セッション変数など)を参照・変更する処理

UDFとストアドプロシージャの違いも比較表で整理します。

UDF / UDTFストアドプロシージャ
呼び出し方法クエリ(SELECTなど)の式やFROM句の中で呼ぶCALL文で単独実行(クエリ式の中では呼べない)
戻り値必ず値(または表)を返す戻り値は1つ返せるが、返すこと自体が主目的ではないことも多い
できること計算・変換(原則として副作用なし。DDL/DMLの発行は不可)内部でSQL文の発行(DDL/DML)、分岐・ループなどの手続き処理
主な用途クエリ内で再利用する計算ロジック管理処理・バッチ処理の自動化(タスクとの組み合わせ)
実装したい要件ロジックをSnowflake内に持たせたい
クエリ内で使う計算(1値)→ スカラーUDF
クエリ内で使う表を返す処理→ UDTF(テーブル関数)
DDL/DMLを含む手続き処理→ ストアドプロシージャ(CALL)
定期的に自動実行したい→ タスクからプロシージャ/SQLを呼ぶ

図:要件 → UDF / UDTF / プロシージャ / タスク の選択分岐

📝 試験のポイント

「SELECTの中で呼べるのはどちらか」(→UDF)、「DML/DDLを実行できるのはどちらか」(→ストアドプロシージャ)、「既定の実行権限はどちらか」(→owner's rights)の3点が、この分野の中心的な出題パターンです。

✅ この節のまとめ

練習問題

問1. ソーステーブルの増分データをターゲットに反映したい。キーが一致する行は更新し、存在しない行は挿入する処理を1文で行う構文はどれか。

  1. INSERT OVERWRITE
  2. MERGE
  3. INSERT ALL
  4. UPDATE ... FROM
解答と解説を見る

正解:B

MERGEはON句の条件で突き合わせ、WHEN MATCHED(更新/削除)とWHEN NOT MATCHED(挿入)を1文で記述するUPSERT構文です。AのINSERT OVERWRITEはテーブルを空にしてから挿入する洗い替えで、既存行の選択的更新はできません。CのINSERT ALLは複数テーブルへの振り分け挿入で、更新はできません。DのUPDATEは既存行の更新のみで、新規行の挿入ができません。

問2. SAMPLEのBERNOULLI方式とSYSTEM方式の説明として正しいものはどれか。

  1. BERNOULLIはブロック単位、SYSTEMは行単位で抽出する
  2. BERNOULLIは行単位で偏りが少なく、SYSTEMはブロック単位のため大テーブルで高速だが偏りが出やすい
  3. どちらも行単位で抽出し、違いは構文だけである
  4. SYSTEM方式ではREPEATABLEを指定できない
解答と解説を見る

正解:B

BERNOULLI(ROW)は各行を独立に選ぶため統計的な偏りが少なく、SYSTEM(BLOCK)はマイクロパーティション単位で選ぶため読み飛ばしができて高速な一方、ブロック内の行がまとめて入るため偏りやすくなります。Aは対応が逆です。Cは誤りで、抽出単位という本質的な違いがあります。Dは誤りで、REPEATABLE(SEED)はSYSTEM方式でも指定でき、同じサンプルを再現できます。

問3. APPROX_COUNT_DISTINCTを使う場面として最も適切なものはどれか。

  1. 請求金額の確定処理で、課金対象ユーザー数を厳密に数える
  2. 数十億行のログから日次のユニークユーザー数の傾向を高速に把握する
  3. 主キー制約の代わりに重複行を検出する
  4. COUNT(*)よりも正確な総行数を得る
解答と解説を見る

正解:B

APPROX_COUNT_DISTINCTはHyperLogLogに基づく近似計算で、誤差数%と引き換えに大テーブルのカーディナリティを高速・省メモリで返します。傾向把握のダッシュボードなどに最適です。AとCは厳密さが要求される処理であり、近似関数は不適切です(COUNT(DISTINCT)等を使います)。Dは誤りで、近似関数が厳密関数より正確になることはなく、総行数ならCOUNT(*)で十分です。

問4. UDFとストアドプロシージャに関する説明として正しいものを2つ選べ。

  1. UDFはSELECT文の式の中で呼び出せるが、ストアドプロシージャはCALL文で実行する
  2. ストアドプロシージャは既定でcaller's rights(呼び出し元の権限)で実行される
  3. ストアドプロシージャの既定はowner's rights(所有者の権限)であり、EXECUTE AS CALLERを指定するとcaller's rightsになる
  4. スカラーUDFは内部でDDL/DML文を自由に発行できる
解答と解説を見る

正解:A、C

呼び出し方法の違い(UDF=クエリ内の式、プロシージャ=CALL)と、実行権限の既定(owner's rights、EXECUTE AS CALLERでcaller's rightsに変更)はどちらも正しい説明です。Bは既定の説明が逆です。Dは誤りで、UDFは計算・変換のための関数であり、DDL/DMLの発行のような副作用を持つ処理はストアドプロシージャの領域です。