dbt (データビルドツール) は何をしますか?
dbtは、SQLを使用してウェアハウス内のデータ変換を管理します。v2.0では、パフォーマンス向上のためのFusion Engine (Rust) が導入されました。
使用するタイミング
- アナリティクスエンジニアリングとデータ変換: モダンなクラウドウェアハウス (Snowflake、BigQuery、Databricks、Redshift) 内で生データを変換します。
- Jinjaとバージョン管理によるモジュラーSQL: DAG依存関係とマクロを使用した再利用可能なSQLモデルを構築します。
- データ品質とスキーマテスト:
unique、not_null、参照整合性、カスタム単体テストを実施します。 - 自動データドキュメントとリネージュ: コードベースのスキーマから対話型の依存関係リネージュグラフを直接生成します。
クイックスタート
-- models/marts/fct_orders.sql
{{ config(materialized='table') }}
with orders as (
select * from {{ ref('stg_orders') }}
),
payments as (
select * from {{ ref('stg_payments') }}
)
select
orders.order_id,
orders.customer_id,
orders.order_date,
coalesce(payments.amount, 0) as total_amount
from orders
left join payments using (order_id)
中核概念
ref() と source() による宣言的SQLモデリング
依存関係を解決した変換モデルの構築:
-- models/marts/core/fct_customer_orders.sql
{{
config(
materialized = 'incremental',
unique_key = 'order_id',
on_schema_change = 'fail'
)
}}
with orders as (
select * from {{ ref('stg_orders') }}
{% if is_incremental() %}
where order_date >= (select coalesce(max(order_date), '1970-01-01') from {{ this }})
{% endif %}
),
customers as (
select * from {{ ref('stg_customers') }}
)
select
o.order_id,
o.customer_id,
c.full_name as customer_name,
o.order_date,
o.total_amount_usd
from orders o
inner join customers c on o.customer_id = c.customer_id
YAMLによるスキーマテストとドキュメント
カラム制約とドキュメントを強制:
# models/marts/core/schema.yml
version: 2
models:
- name: fct_customer_orders
description: "ステージングされた注文と顧客ディメンションを組み合わせた日次顧客注文ファクトテーブル。"
columns:
- name: order_id
description: "注文の主キー。"
tests:
- unique
- not_null
- name: customer_id
description: "ステージング顧客を参照する外部キー。"
tests:
- not_null
- relationships:
to: ref('stg_customers')
field: customer_id
- name: total_amount_usd
description: "USDでの取引合計金額。"
tests:
- not_null
DRYな再利用可能ロジックのためのJinjaマクロ
カスタムの再利用可能なSQLユーティリティを作成:
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name, decimal_places=2) -%}
round(cast(({{ column_name }} / 100.0) as numeric), {{ decimal_places }})
{%- endmacro %}
-- モデル内での使用法:
select
id,
{{ cents_to_dollars('amount_in_cents') }} as amount_usd
from {{ ref('stg_payments') }}
一般的なパターン
タイムスタンプフィルタリングによるインクリメンタルモデル
問題: 数十億行のテーブルをdbtの実行のたびに毎回全体を再構築するのは、遅くコストがかかります。
解決策:
is_incremental() とともに incremental マテリアライゼーションを使用:
{{ config(
materialized='incremental',
unique_key='event_id'
) }}
select * from {{ source('raw', 'events') }}
{% if is_incremental() %}
-- ターゲットテーブルの最新イベント以降に作成された行のみをクエリ
where event_timestamp > (select max(event_timestamp) from {{ this }})
{% endif %}
ベストプラクティス (2026)
- 行うべき: プロジェクトを標準的なレイヤー (
staging(生ソースと1対1)、intermediate、marts) に整理します。 - 行うべき: DAGリネージュを維持するために、常に
{{ ref('model_name') }}と{{ source('source_name', 'table_name') }}を使用します。 - 行うべき: 数百万行規模のファクトテーブルにはインクリメンタルモデル (
materialized='incremental') を実装します。 - 行うべき: すべてのプルリクエストに対してCIパイプラインで
dbt testを実行し、データスキーマの後退を検出します。 - 行うべきでない: 生のデータベーステーブル参照 (例:
analytics.raw.users) を記述せず、常にsource()またはref()を使用します。 - 行うべきでない: ステージングモデル内でビジネスロジックを実行せず、ステージングでは列のクリーニング、キャスト、リネームのみを行います。
- 行うべきでない: 環境 (dev 対 prod) をハードコードせず、profiles.yml で
target.name条件分岐を使用します。
トラブルシューティング
| エラー | 原因 | 解決策 |
|---|---|---|
Compilation Error: Model '...' depends on a node named '...' which was not found | {{ ref('model_name') }} のタイポ、またはモデルファイルが存在しない。 | モデルファイル名が参照文字列と完全に一致するか確認する。 |
dbt test failed: unique constraint violated | 非一意な結合条件により重複キーが生成された。 | target/compiled/ 内のコンパイル済みSQLで重複キーを調査する。 |
Schema change error during incremental run | 上流モデルで列が追加/削除され、既存のインクリメンタルテーブルが壊れた。 | スキーマを再構築するために dbt run --full-refresh を一度実行する。 |