第4章 パフォーマンス最適化・クエリ・変換(Performance & Transformation, 21%)
🎯 この節の学習目標
日々の変換処理でよく使う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;
巨大テーブルの傾向を素早く掴みたいとき、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)でシード値を固定すると、テーブルが変更されない限り同じ行の集合が返り、検証の再現性を確保できます。
「ユニークユーザー数」のようなカーディナリティ(個別値数)の厳密な計算は、大テーブルではメモリと時間を大量に消費します。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 などがあります。ダッシュボードの傾向把握など「厳密さより速度」が求められる場面で使い、課金や会計など厳密さが必要な集計には使わない、という使い分けが論点です。
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)として作成すると、関数の定義(ロジック)が権限のないユーザーから見えなくなり、オプティマイザによる内部情報の露出も防がれます。データ共有で関数を提供する場合などに使いますが、最適化の一部が抑制されるため性能が低下することがあります。
ストアドプロシージャは、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)、分岐・ループなどの手続き処理 |
| 主な用途 | クエリ内で再利用する計算ロジック | 管理処理・バッチ処理の自動化(タスクとの組み合わせ) |
図:要件 → UDF / UDTF / プロシージャ / タスク の選択分岐
📝 試験のポイント
「SELECTの中で呼べるのはどちらか」(→UDF)、「DML/DDLを実行できるのはどちらか」(→ストアドプロシージャ)、「既定の実行権限はどちらか」(→owner's rights)の3点が、この分野の中心的な出題パターンです。
✅ この節のまとめ
問1. ソーステーブルの増分データをターゲットに反映したい。キーが一致する行は更新し、存在しない行は挿入する処理を1文で行う構文はどれか。
正解:B
MERGEはON句の条件で突き合わせ、WHEN MATCHED(更新/削除)とWHEN NOT MATCHED(挿入)を1文で記述するUPSERT構文です。AのINSERT OVERWRITEはテーブルを空にしてから挿入する洗い替えで、既存行の選択的更新はできません。CのINSERT ALLは複数テーブルへの振り分け挿入で、更新はできません。DのUPDATEは既存行の更新のみで、新規行の挿入ができません。
問2. SAMPLEのBERNOULLI方式とSYSTEM方式の説明として正しいものはどれか。
正解:B
BERNOULLI(ROW)は各行を独立に選ぶため統計的な偏りが少なく、SYSTEM(BLOCK)はマイクロパーティション単位で選ぶため読み飛ばしができて高速な一方、ブロック内の行がまとめて入るため偏りやすくなります。Aは対応が逆です。Cは誤りで、抽出単位という本質的な違いがあります。Dは誤りで、REPEATABLE(SEED)はSYSTEM方式でも指定でき、同じサンプルを再現できます。
問3. APPROX_COUNT_DISTINCTを使う場面として最も適切なものはどれか。
正解:B
APPROX_COUNT_DISTINCTはHyperLogLogに基づく近似計算で、誤差数%と引き換えに大テーブルのカーディナリティを高速・省メモリで返します。傾向把握のダッシュボードなどに最適です。AとCは厳密さが要求される処理であり、近似関数は不適切です(COUNT(DISTINCT)等を使います)。Dは誤りで、近似関数が厳密関数より正確になることはなく、総行数ならCOUNT(*)で十分です。
問4. UDFとストアドプロシージャに関する説明として正しいものを2つ選べ。
正解:A、C
呼び出し方法の違い(UDF=クエリ内の式、プロシージャ=CALL)と、実行権限の既定(owner's rights、EXECUTE AS CALLERでcaller's rightsに変更)はどちらも正しい説明です。Bは既定の説明が逆です。Dは誤りで、UDFは計算・変換のための関数であり、DDL/DMLの発行のような副作用を持つ処理はストアドプロシージャの領域です。