dbt 2026年版ガイド:データ変換、テスト戦略、面接対策の完全解説

dbtを使ったデータ変換の基礎から実践まで、レイヤードモデリング、インクリメンタル戦略、テスト手法、そして2026年のデータエンジニアリング面接で頻出する質問をコード例とともに解説する。

dbt data transformations and testing tutorial 2026

dbt(data build tool)は、モダンデータウェアハウス内でのデータ変換における標準フレームワークとして定着しており、2026年現在、本番環境で40,000社以上が採用している。本チュートリアルでは、dbtによるデータ変換の仕組み、テスト戦略、そしてデータエンジニアリング面接で実際に問われる質問について、コード例を交えて解説する。

dbtの役割とは

dbtはELTにおける「T」(Transform)を担当するツールである。SQLのSELECT文をDDL(CREATE TABLE、CREATE VIEW、MERGE)にコンパイルし、Snowflake、BigQuery、Redshift、Databricksなどのウェアハウス上で実行する。バージョン管理、依存関係の解決、テスト、ドキュメント生成が標準で組み込まれている。

レイヤードモデリング:Staging、Intermediate、Marts

構造化されたdbtプロジェクトでは、変換処理を3つのレイヤーに分離する。この手法はdbtコミュニティで広く支持されており、各モデルの責務を単一に保ち、デバッグを容易にする。

Stagingモデルは生データに最も近いレイヤーに位置する。カラム名の変更、型のキャスト、不要な行のフィルタリングが主な役割であり、結合や集約は行わない。

sql
-- models/staging/stg_orders.sql
WITH source AS (
    SELECT * FROM {{ source('ecommerce', 'raw_orders') }}
)
SELECT
    id AS order_id,
    customer_id,
    CAST(order_date AS DATE) AS order_date,
    CAST(amount AS DECIMAL(10, 2)) AS order_amount,
    LOWER(status) AS order_status
FROM source
WHERE id IS NOT NULL

Intermediateモデルはビジネスロジックを適用する場所である。結合、集約、ウィンドウ関数などを使い、ref()を通じてStagingモデルを参照することでDAGに依存関係を登録する。

sql
-- models/intermediate/int_customer_orders.sql
WITH orders AS (
    SELECT * FROM {{ ref('stg_orders') }}
),
customers AS (
    SELECT * FROM {{ ref('stg_customers') }}
)
SELECT
    c.customer_id,
    c.customer_name,
    COUNT(o.order_id) AS total_orders,
    SUM(o.order_amount) AS lifetime_value,
    MIN(o.order_date) AS first_order_date,
    MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_id, c.customer_name

Martsは最終的な消費レイヤーであり、ダッシュボードやアナリストが直接クエリする、整理されたドキュメント付きのテーブルを提供する。

このレイヤー構造により、ソースに障害が発生してもStagingレイヤーにのみ影響し、パイプライン全体に波及しない。各レイヤーは独立してテスト可能である。

ref()関数とDAGの依存解決

ref()は単なるエイリアスではない。{{ ref('stg_orders') }}を呼び出すと、コンパイル時に完全修飾テーブル名が解決されると同時に、dbtのDAG(有向非巡回グラフ)に依存関係のエッジが登録される。ref()を使わなければ、dbtは実行順序を決定できない。

よくある誤りは、ref()を使わずにテーブル名を直接記述することである。これは依存関係の追跡を破壊し、上流の依存先が準備完了する前にモデルが実行される可能性がある。

sql
-- 悪い例:ハードコーディングされた参照、依存追跡なし
SELECT * FROM analytics.stg_orders

-- 良い例:ref()で依存関係を登録
SELECT * FROM {{ ref('stg_orders') }}

DAGはdbt run --select stg_orders+のような機能も支える。これはモデルとその下流すべてを実行するコマンドで、ソーススキーマの変更後にピンポイントで再ビルドする際に有用である。

マテリアライゼーション:適切なストレージ戦略の選択

dbtには4つのビルトインマテリアライゼーションが用意されており、アクセスパターンやデータ量に応じて使い分ける。

| マテリアライゼーション | ストレージ | 用途 | トレードオフ | |----------------|---------|----------|----------| | view | ストレージなし(読み取り時に計算) | 軽量な変換、小規模データ | 大量データでの読み取りが遅い | | table | 毎回テーブルを完全再構築 | Martレイヤー、高速読み取り | 全データを再構築、コスト高 | | incremental | 新規行のみ追加/マージ | 大規模ファクトテーブル、イベントストリーム | ロジックが複雑、unique_keyが必要 | | ephemeral | CTEとしてインライン化、実体化しない | モデル間で共有する再利用ロジック | 直接クエリ不可 |

デフォルトはviewである。モデルのconfigブロックまたはdbt_project.ymlで上書きできる。

sql
-- models/marts/fct_daily_revenue.sql
{{
    config(
        materialized='incremental',
        unique_key='revenue_date',
        incremental_strategy='merge'
    )
}}
SELECT
    DATE(order_date) AS revenue_date,
    SUM(order_amount) AS daily_revenue,
    COUNT(DISTINCT customer_id) AS unique_customers
FROM {{ ref('stg_orders') }}
WHERE order_status = 'completed'
{% if is_incremental() %}
    AND order_date > (SELECT MAX(revenue_date) FROM {{ this }})
{% endif %}
GROUP BY DATE(order_date)

is_incremental()ブロックはインクリメンタルビルド時のみ実行される。フルリフレッシュ(dbt run --full-refresh)ではスキップされ、テーブル全体が再構築される。

Data Engineeringの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

各レイヤーでのデータ品質テスト

dbtには2種類のテストが用意されている。YAMLで宣言するジェネリックテストと、失敗時に行を返すスタンドアロンSQLファイルとして記述するシングルテストである。

ジェネリックテストは構造的な制約を検証する。

yaml
# models/staging/_stg_models.yml
version: 2

models:
  - name: stg_orders
    columns:
      - name: order_id
        tests:
          - unique           # 注文IDの重複なし
          - not_null         # 全行に注文IDが存在
      - name: order_status
        tests:
          - accepted_values:
              values: ['completed', 'pending', 'cancelled', 'refunded']
      - name: customer_id
        tests:
          - not_null
          - relationships:   # 参照整合性チェック
              to: ref('stg_customers')
              field: customer_id

シングルテストは、ジェネリックテストでは表現できないビジネスルールを検証する。

sql
-- tests/assert_revenue_never_negative.sql
-- 日次収益がマイナスの行を返す(0行が期待値)
SELECT
    revenue_date,
    daily_revenue
FROM {{ ref('fct_daily_revenue') }}
WHERE daily_revenue < 0

ユニットテストはdbt 1.8で導入された機能であり、制御された入力と期待される出力で変換ロジックを検証する。ウェアハウスへの接続は不要で、dbtのテストフェーズで直接実行される。

テスト戦略はデータフローに沿って構成すべきである。ソースレベルの鮮度チェックと行数検証、Stagingレベルのスキーマテスト(not_nullunique)、Intermediateレベルの参照整合性、Martレベルのビジネスロジックアサーションという順序が推奨される。

dbt Core v1.10とFusionエンジン

dbt Core v1.10では、runおよびbuildコマンド向けに--sampleフラグが導入された。このフラグはref()source()に時間ベースのサンプリングを適用し、フルビルドのコストをかけずにデータのサブセットで変換を検証できる。数十億行のファクトテーブルを持つモデルの開発イテレーションに有効である。

dbt FusionエンジンはRustで一から再構築されたもので、dbt CloudのSnowflake、BigQuery、Redshift、Databricks環境において新規プロジェクトのデフォルトとなった。Fusionはセマンティックバージョニング(2.0から開始)を導入し、コンパイルと実行の両方でパフォーマンスを大幅に向上させている。

2026年のその他の注目すべき追加機能として、一元的なメトリクス定義のためのSemantic Layer YAML仕様、モデルごとのウェアハウスコンピュートを見積もるCost Insights(ベータ版)、そしてネイティブプライベートパッケージの一般提供が挙げられる。

マクロとJinjaによる再利用可能なロジック

同じSQLパターンが複数のモデルで繰り返される場合、マクロとして抽出する。マクロはmacros/ディレクトリに格納されるJinja関数である。

sql
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
    ROUND(CAST({{ column_name }} AS DECIMAL(10, 4)) / 100, 2)
{% endmacro %}

任意のモデルから呼び出せる。

sql
-- models/staging/stg_payments.sql
SELECT
    payment_id,
    order_id,
    {{ cents_to_dollars('amount_cents') }} AS amount_dollars,
    payment_method
FROM {{ source('stripe', 'payments') }}

これによりコードベースのDRY原則が保たれる。変換式を必要なすべてのモデルにコピーする代替手段は、保守コストの増大と不整合の原因となる。

データエンジニアリング面接で実際に問われる質問

2026年のデータエンジニアリング面接では、基礎的な知識にとどまらないdbtの理解が問われる傾向にある。以下は、モダンデータスタックを採用する企業の採用パターンから抽出した、候補者を差別化する質問である。

Q:インクリメンタルモデルが夜間に全テーブルを再処理した。原因は何か?

最も一般的な原因は、unique_keyカラムにNULLが含まれていたことである。dbtがNULLキーでMERGEを実行すると、すべての行でマッチが失敗し、重複挿入が発生する。2番目の原因は、dbt run --full-refreshが意図せず実行されたケースである。このコマンドはテーブルをゼロから再構築する。3番目の原因は、is_incremental()フィルタがターゲットテーブルにまだ存在しないカラムを参照しているケース(初回実行と2回目以降で挙動が異なる)である。デバッグはtarget/compiled/のコンパイル済みSQLの確認から始まる。

Q:本番dbtプロジェクトでテストをどのように階層化すべきか?

ソースの鮮度チェックが最初に実行される。ソースが更新されていなければ、下流モデルは古いデータで実行すべきではない。Stagingのテストではスキーマ制約(主キーのunique + not_null、許容値、型キャスト)を検証する。Intermediateのテストでは結合間の参照整合性を確認する。Martのテストではビジネス不変条件(収益 >= 0、アクティブユーザー数 >= 課金ユーザー数、部分の合計 = 全体)をアサートする。すべてのテストはプルリクエスト時にdev環境のスキーマに対してCIで実行し、デプロイ後は本番環境でアラート付きで再実行する。

Q:モデルをephemeralにすべき場合とviewにすべき場合の使い分けは?

ephemeralモデルはSQLをCTEとして消費モデルにインライン化する。独立してクエリする必要のない再利用ロジックに適している。viewは読み取り時に計算され、クエリ可能なオブジェクトとして存在する。トレードオフとして、ephemeralモデルは直接テストできず(アサーションを実行するテーブルが存在しない)、データリネージツールにも表示されない。内部的なヘルパーロジックにはephemeralを、独立したテストやモニタリングが必要なものにはviewを使用する。

Q:source()ref()の違いを説明せよ。

source()sources.ymlファイルで定義された生テーブルを指す。これらはdbtが管理しないテーブルである。ref()は他のdbtモデルを指す。両方ともDAGの依存関係を登録するが、source()は鮮度チェック(dbt source freshness)も有効にする点がref()との違いである。生テーブルにはsource()を、それ以外にはref()を使うことは任意ではなく、dbtが管理対象と非管理対象を区別するための仕組みである。

より広範なデータエンジニアリング面接の質問については、ETL vs ELTパイプラインアーキテクチャなどのトピックも含め、SharpSkillのdbt基礎dbt応用パターンのインタラクティブ演習で網羅的に学習できる。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

まとめ

  • dbtはSQL SELECT文をDDLに自動コンパイルし、ウェアハウス内の生データを変換する。Staging/Intermediate/Martのレイヤー構造により、各モデルは単一の責務に集中できる
  • ref()関数は依存関係管理の要であり、テーブル名の解決とDAGの構築による実行順序の制御を担う
  • インクリメンタルマテリアライゼーションは大規模テーブルのコストを削減するが、unique_key、NULL値、is_incremental()フィルタの慎重な取り扱いが求められる
  • テストはデータフローに沿って構成する。ソースの鮮度チェック、Stagingでのスキーマ制約、Intermediateでの参照整合性、Martでのビジネスロジックアサーションの順序が推奨される
  • dbt Core v1.10は高速なイテレーションのための--sampleフラグを導入し、Fusionエンジン(Rustベース、バージョン2.0)がdbt Cloudのデフォルトとなった
  • dbtに関する面接質問は定義の暗記ではなく、実際の障害のデバッグ(インクリメンタル再処理、NULLキー、陳腐化したソース)に焦点を当てている。コンパイル済みSQLとCIパイプラインの実践経験が重要視される

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#dbt
#data-engineering
#sql
#data-transformation
#interview

共有

関連記事