Databricks認定試験(DEA・DEP・Spark Developer)では、Spark SQLの正確な構文知識が合否を分けます。特にData Engineer AssociateのELT with Spark SQL & Python(29%)ドメインでは、SQLクエリの読み書きスキルが直接問われます。この記事では、CREATE TABLE・MERGE INTO・Window関数・CTE・Delta Lake操作・高階関数・PIVOT/UNPIVOTまで、試験に出る構文を実行可能なSQLコード付きで体系的に整理しています。
以下は、Databricks認定試験における各Spark SQL構文の出題頻度を整理した表です。学習の優先順位付けに活用してください。
| 構文 | 頻出度 | 主な出題試験 | 出題パターン |
|---|---|---|---|
| MERGE INTO | ★★★★★ | DEA / DEP / Spark | Upsert構文、SCD Type 2、条件分岐 |
| Window関数 | ★★★★★ | DEA / Spark / DAA | ROW_NUMBER重複排除、RANK比較、LAG/LEAD差分 |
| OPTIMIZE / VACUUM | ★★★★☆ | DEA / DEP | 機能の違い、ZORDER、RETAIN期間 |
| CTE(WITH句) | ★★★★☆ | DEA / Spark / DAA | Window関数との組み合わせ、複数CTE連結 |
| CREATE TABLE / CTAS | ★★★☆☆ | DEA / DEP | マネージド vs 外部、スキーマ推論 |
| COPY INTO | ★★★☆☆ | DEA / DEP | Auto Loaderとの比較、冪等性 |
| DESCRIBE HISTORY / RESTORE | ★★★☆☆ | DEA / DEP | Time Travelとの関連、復元手順 |
| 高階関数 | ★★☆☆☆ | DEA / Spark | TRANSFORM/FILTERの配列操作構文 |
| PIVOT / UNPIVOT | ★★☆☆☆ | DAA / Spark | 行列変換の構文理解 |
Delta LakeテーブルのCREATE文は3つの主要パターンがあります。マネージドテーブル・外部テーブルの違いと、DROP TABLE時のデータ削除の有無を正確に把握しましょう。
LOCATIONを指定しないCREATE TABLE文はマネージドテーブルを作成します。Unity Catalogがデータの格納場所を管理し、DROP TABLE実行時にメタデータ・データファイルの両方が削除されます。
-- マネージドテーブル: スキーマ定義で作成
CREATE TABLE catalog_name.schema_name.orders (
order_id BIGINT,
customer_id BIGINT,
order_date DATE,
amount DECIMAL(10, 2),
status STRING
) USING DELTA
COMMENT 'Customer order transactions'
TBLPROPERTIES (
'delta.autoOptimize.optimizeWrite' = 'true',
'delta.autoOptimize.autoCompact' = 'true'
);LOCATION句で外部ストレージのパスを指定すると外部テーブルになります。DROP TABLEを実行してもメタデータのみ削除され、データファイルは外部ストレージに残ります。
-- 外部テーブル: データは指定パスに格納
CREATE TABLE catalog_name.schema_name.events (
event_id BIGINT,
event_type STRING,
event_ts TIMESTAMP,
payload STRING
) USING DELTA
LOCATION 's3://my-bucket/bronze/events';CREATE OR REPLACE TABLE(CORT)は既存テーブルを上書きで再作成します。CTASはSELECTの結果からスキーマを自動推論してテーブルを作成するため、カラム定義が不要です。
-- CTAS: SELECTの結果をテーブル化(スキーマ自動推論)
CREATE OR REPLACE TABLE silver.daily_summary AS
SELECT
order_date,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount
FROM bronze.orders
WHERE status = 'completed'
GROUP BY order_date;-- セッションスコープ(セッション終了で消滅)
CREATE OR REPLACE TEMP VIEW v_active_users AS
SELECT * FROM users WHERE is_active = true;
-- クラスタースコープ(global_temp.で参照)
CREATE OR REPLACE GLOBAL TEMP VIEW v_summary AS
SELECT region, COUNT(*) AS cnt FROM sales GROUP BY region;
SELECT * FROM global_temp.v_summary;INSERT INTOは既存データに追記します。INSERT OVERWRITEはテーブル(またはパーティション)の既存データを削除してから書き込みます。Delta Lakeではパーティション指定の有無で動作が変わる点に注意してください。
-- 追記: 既存データは保持される
INSERT INTO silver.orders
SELECT * FROM bronze.orders_raw
WHERE process_date = current_date();
-- 上書き: テーブル全体を置換
INSERT OVERWRITE silver.orders
SELECT * FROM bronze.orders_raw;
-- パーティション単位の上書き(動的パーティション上書き)
INSERT OVERWRITE silver.orders
PARTITION (order_date)
SELECT order_id, customer_id, amount, status, order_date
FROM bronze.orders_raw;外部ファイル(CSV/JSON/Parquet等)からDelta Lakeテーブルにデータをロードするコマンドです。処理済みファイルを自動追跡し、同じファイルの重複取り込みを防ぐ冪等性を持ちます。ファイル数が数千件程度の小規模バッチ向けで、大規模・継続的な取り込みにはAuto Loaderが推奨されます。
COPY INTO silver.transactions
FROM 's3://data-lake/landing/transactions/'
FILEFORMAT = JSON
FORMAT_OPTIONS (
'inferSchema' = 'true',
'mergeSchema' = 'true',
'dateFormat' = 'yyyy-MM-dd'
)
COPY_OPTIONS (
'mergeSchema' = 'true',
'force' = 'false' -- true にすると重複取り込み防止を無効化
);MERGE INTOはソーステーブルとターゲットテーブルをON条件でマッチングし、WHEN MATCHED / WHEN NOT MATCHED / WHEN NOT MATCHED BY SOURCEの3分岐でINSERT・UPDATE・DELETEを一括実行するコマンドです。Databricks試験全体で最も出題頻度の高いSQL構文です。
MERGE INTO gold.customers AS target
USING staging.customer_updates AS source
ON target.customer_id = source.customer_id
WHEN MATCHED THEN
UPDATE SET
target.name = source.name,
target.email = source.email,
target.updated_at = current_timestamp()
WHEN NOT MATCHED THEN
INSERT (customer_id, name, email, created_at, updated_at)
VALUES (source.customer_id, source.name, source.email,
current_timestamp(), current_timestamp());MERGE INTO gold.products AS t
USING staging.product_feed AS s
ON t.product_id = s.product_id
-- 条件付きUPDATE: 更新日が新しい場合のみ
WHEN MATCHED AND s.updated_at > t.updated_at THEN
UPDATE SET *
-- ソースに存在しない行を論理削除
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET t.is_active = false, t.deleted_at = current_timestamp()
-- 新規行はINSERT
WHEN NOT MATCHED BY TARGET THEN
INSERT *;SCD Type 2は変更履歴を保持するディメンション管理手法です。既存レコードのend_dateを設定して無効化し、新バージョンの行をINSERTします。
MERGE INTO dim.customers AS t
USING (
SELECT s.*, true AS is_new
FROM staging.customers s
JOIN dim.customers d
ON s.customer_id = d.customer_id
AND d.is_current = true
AND (s.name <> d.name OR s.email <> d.email)
UNION ALL
SELECT s.*, true AS is_new
FROM staging.customers s
WHERE NOT EXISTS (
SELECT 1 FROM dim.customers d
WHERE d.customer_id = s.customer_id
)
) AS source
ON t.customer_id = source.customer_id AND t.is_current = true
WHEN MATCHED THEN
UPDATE SET t.is_current = false, t.end_date = current_date()
WHEN NOT MATCHED THEN
INSERT (customer_id, name, email, start_date, end_date, is_current)
VALUES (source.customer_id, source.name, source.email,
current_date(), NULL, true);OPTIMIZEは小さなファイルを統合して読み取り性能を向上させるCompactionコマンドです。ZORDER BYを併用すると、指定カラムの値でデータをコロケーション(同じカラム値のデータを物理的に近くに配置)し、フィルタクエリのスキップ効率を高めます。
-- 基本Compaction
OPTIMIZE gold.sales;
-- ZORDERでデータをコロケーション
OPTIMIZE gold.sales ZORDER BY (region, product_id);
-- WHERE句で対象パーティションを限定
OPTIMIZE gold.sales
WHERE order_date >= '2026-01-01'
ZORDER BY (customer_id);現在のテーブルバージョンから参照されていない古いデータファイルを物理削除し、ストレージコストを削減します。削除されたバージョンのTime Travelは使えなくなります。
-- デフォルト: 168時間(7日)より古い不要ファイルを削除
VACUUM gold.sales;
-- 保持期間を明示的に指定
VACUUM gold.sales RETAIN 720 HOURS; -- 30日間
-- DRY RUN: 削除対象ファイルの一覧のみ表示(削除しない)
VACUUM gold.sales DRY RUN;| 比較項目 | OPTIMIZE | VACUUM |
|---|---|---|
| 目的 | 小ファイル統合(Compaction) | 不要ファイルの物理削除 |
| 効果 | クエリ読み取り性能の向上 | ストレージコストの削減 |
| データへの影響 | 論理的な変更なし(読み取り結果は同一) | 古いバージョンのTime Travelが不可に |
| 推奨タイミング | 書き込みパイプライン後に定期実行 | OPTIMIZEの後に実行 |
-- トランザクション履歴(バージョン・操作・タイムスタンプ)
DESCRIBE HISTORY gold.sales;
DESCRIBE HISTORY gold.sales LIMIT 5;
-- テーブル詳細情報(ファイル数・サイズ・パーティション等)
DESCRIBE DETAIL gold.sales;-- 特定バージョンに復元(実際にデータを戻す)
RESTORE TABLE gold.sales TO VERSION AS OF 12;
-- タイムスタンプ指定で復元
RESTORE TABLE gold.sales TO TIMESTAMP AS OF '2026-03-20 10:00:00';
-- Time Travel: 過去バージョンを参照のみ(復元しない)
SELECT * FROM gold.sales VERSION AS OF 12;
SELECT * FROM gold.sales TIMESTAMP AS OF '2026-03-20';
-- Time Travel構文でのテーブル指定(@記法)
SELECT * FROM gold.sales@v12;Window関数はGROUP BYのように行を集約せず、各行を保持したまま集計・順位付け・前後行参照を行います。OVER句でパーティションと並び順を指定します。
以下の表は、同じデータセット(department内のsalary降順)で各関数の出力がどう異なるかを示しています。
| name | dept | salary | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|---|---|
| Alice | Sales | 90000 | 1 | 1 | 1 |
| Bob | Sales | 85000 | 2 | 2 | 2 |
| Carol | Sales | 85000 | 3 | 2 | 2 |
| Dave | Sales | 78000 | 4 | 4 | 3 |
BobとCarolが同額85000の場合: ROW_NUMBERは任意の順で2,3を振ります。RANKは両者に2を付け、次を4(3をスキップ)にします。DENSE_RANKは両者に2を付け、次を3(スキップしない)にします。
-- 各顧客の最新注文のみ取得(CTE + ROW_NUMBERパターン)
WITH ranked AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date DESC
) AS rn
FROM silver.orders
)
SELECT order_id, customer_id, order_date, amount
FROM ranked
WHERE rn = 1;-- 前月比の売上差分を計算
SELECT
month,
product_id,
revenue,
LAG(revenue, 1) OVER (PARTITION BY product_id ORDER BY month) AS prev_month,
LEAD(revenue, 1) OVER (PARTITION BY product_id ORDER BY month) AS next_month,
revenue - LAG(revenue, 1) OVER (PARTITION BY product_id ORDER BY month) AS mom_diff
FROM gold.monthly_sales;-- 累積売上と移動平均
SELECT
order_date,
daily_revenue,
SUM(daily_revenue) OVER (ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_revenue,
AVG(daily_revenue) OVER (ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg_7d
FROM gold.daily_sales;CTEはWITH句で名前付き結果セットを定義し、後続のSELECTで参照する構文です。サブクエリと異なり、複数箇所から同じCTEを参照でき、クエリの可読性が大幅に向上します。
WITH active_customers AS (
SELECT customer_id, name, region
FROM gold.customers
WHERE is_active = true
),
recent_orders AS (
SELECT customer_id, SUM(amount) AS total_spent
FROM silver.orders
WHERE order_date >= add_months(current_date(), -3)
GROUP BY customer_id
),
customer_summary AS (
SELECT
ac.customer_id,
ac.name,
ac.region,
COALESCE(ro.total_spent, 0) AS total_spent_3m
FROM active_customers ac
LEFT JOIN recent_orders ro ON ac.customer_id = ro.customer_id
)
SELECT region, COUNT(*) AS customer_count, SUM(total_spent_3m) AS region_revenue
FROM customer_summary
GROUP BY region
ORDER BY region_revenue DESC;Window関数のエイリアスはWHERE句で直接フィルタできません(SQLの評価順序でWHEREはSELECTより先に評価)。CTEでWindow関数を計算した後、外側のクエリでフィルタするのが定石です。
-- 各部門のトップ3給与者を取得
WITH salary_rank AS (
SELECT
employee_id, name, department,
salary,
DENSE_RANK() OVER (
PARTITION BY department ORDER BY salary DESC
) AS dr
FROM hr.employees
)
SELECT employee_id, name, department, salary
FROM salary_rank
WHERE dr <= 3
ORDER BY department, dr;Spark SQLの高階関数は、配列型カラムに対してラムダ式で要素単位の操作を行います。EXPLODE→集計→COLLECT_LISTの多段処理を1行で表現でき、ネスト構造のETL処理に有効です。
配列の各要素に関数を適用して新しい配列を返します。
-- 各要素を2倍に変換
SELECT TRANSFORM(ARRAY(1, 2, 3, 4), x -> x * 2);
-- 結果: [2, 4, 6, 8]
-- 注文の各商品に税率10%を加算
SELECT
order_id,
TRANSFORM(item_prices, price -> price * 1.10) AS prices_with_tax
FROM orders;条件に合致する要素のみを抽出した新しい配列を返します。
-- 偶数のみ抽出
SELECT FILTER(ARRAY(1, 2, 3, 4, 5, 6), x -> x % 2 = 0);
-- 結果: [2, 4, 6]
-- 1000円以上の商品のみ残す
SELECT
order_id,
FILTER(item_prices, p -> p >= 1000) AS high_value_items
FROM orders;配列の要素をアキュムレータで畳み込み、単一の値にします。
-- 配列要素の合計を計算
SELECT REDUCE(ARRAY(1, 2, 3, 4), 0, (acc, x) -> acc + x);
-- 結果: 10
-- 最大値を取得
SELECT REDUCE(
ARRAY(35, 12, 88, 47), 0,
(acc, x) -> CASE WHEN x > acc THEN x ELSE acc END
);
-- 結果: 88PIVOTは行データを列に変換(行→列)、UNPIVOTは列データを行に変換(列→行)する構文です。クロス集計レポートの生成やELTのデータ整形で使われます。
-- 四半期別の売上を列方向に展開
SELECT *
FROM (
SELECT region, quarter, revenue
FROM gold.quarterly_sales
)
PIVOT (
SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4')
);
-- 結果: region | Q1 | Q2 | Q3 | Q4-- 列方向のデータを行方向に変換
SELECT *
FROM gold.quarterly_sales_wide
UNPIVOT (
revenue FOR quarter IN (Q1, Q2, Q3, Q4)
);
-- 結果: region | quarter | revenue(各四半期が1行ずつ)MERGE INTO - 条件付き分岐
問題 1
以下のMERGE INTO文について正しい記述はどれですか? MERGE INTO gold.inventory AS t USING staging.stock_updates AS s ON t.sku = s.sku WHEN MATCHED AND s.quantity = 0 THEN DELETE WHEN MATCHED THEN UPDATE SET t.quantity = s.quantity, t.updated_at = current_timestamp() WHEN NOT MATCHED THEN INSERT (sku, quantity, created_at, updated_at) VALUES (s.sku, s.quantity, current_timestamp(), current_timestamp())
正解: B
MERGE INTOでは複数のWHEN MATCHED句を記述でき、先に条件付きの句(AND s.quantity = 0)、次に条件なしの句を配置します。マッチした行はまず最初の条件(quantity = 0)で評価され、該当すればDELETEされます。該当しない場合は次のWHEN MATCHED句でUPDATEされます。マッチしなかったソース行はWHEN NOT MATCHEDでINSERTされます。選択肢Aは全行DELETEではなくquantity=0の行のみ。選択肢CはDelta Lakeでは複数WHEN MATCHED句が有効。選択肢DはWHEN NOT MATCHED句は標準的な構文要素です。
MERGE INTOで複数のWHEN MATCHED句を書けますか?
はい、Databricks(Delta Lake)ではWHEN MATCHED句を最大2つまで記述できます。1つ目に追加条件付き(例: WHEN MATCHED AND source.updated_at > target.updated_at THEN UPDATE SET *)、2つ目に条件なし(例: WHEN MATCHED THEN DELETE)のように指定します。ただし条件なしの句は最後に1つだけ配置する必要があり、先に条件付きの句を書く順序が求められます。標準SQLではWHEN MATCHED句は1つのみですが、Delta Lakeの拡張構文として複数記述が可能です。
Window関数のROW_NUMBER・RANK・DENSE_RANKはどう使い分けますか?
ROW_NUMBERは同値でも必ず一意の連番を振るため、重複排除(各グループの最新1件を取得)に使います。RANKは同値に同じ順位を付け次の順位をスキップする(1,2,2,4)ため、スポーツのランキングのような「同着後に欠番が出る」順位付けに適しています。DENSE_RANKは同値に同じ順位を付けつつ次の順位をスキップしない(1,2,2,3)ため、「上位N種類を取得」する場面で使います。試験では3つの出力結果の違いが問われるので、同値があるデータセットでの出力を暗記しておくのが有効です。
VACUUMを実行するとTime Travelはどうなりますか?
VACUUMで削除されたデータファイルに対応するバージョンはTime Travel(VERSION AS OF / TIMESTAMP AS OF)で参照できなくなります。デフォルトのRETAIN期間は168時間(7日間)で、delta.deletedFileRetentionDurationプロパティで変更可能です。RETAIN 0 HOURSを指定すると全ての古いファイルが即座に削除されますが、spark.databricks.delta.retentionDurationCheck.enabledをfalseにしないとエラーになります。本番環境では最低7日間の保持期間を推奨します。試験ではVACUUM後のTime Travel不可がよく問われます。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Databricks資格一覧|全7試験・難易度・勉強法
Databricks認定資格全7試験の一覧・難易度・出題範囲・合格ラインを徹底解説。2026年最新版の公式試験ガイドに準...
Databricks試験の難易度ランキング|全7資格を徹底比較
Databricks認定全7試験の難易度をランキング形式で徹底比較。合格率・学習時間・出題傾向から難易度を分析。...
Databricks資格の勉強方法|最短合格ルートと学習時間の目安
Databricks認定資格に最短で合格するための勉強方法を完全ガイド。公式リソース・問題集・学習スケジュールを徹底解説...
Databricks Data Engineer Associate完全解説|出題範囲・問題例・合格戦略
Databricks Certified Data Engineer Associate試験を徹底解説。5つの出題ドメイ...
Databricks Data Engineer Professional完全解説|上級試験の攻略法
Databricks Certified Data Engineer Professional試験を徹底解説。10の出題...