dbtの高度な機能
田中VPoE「dbtの基本は身についたね。ここからは、実運用で必要になる高度な機能を学ぼう。マクロ、パッケージ、hooks、そして運用のベストプラクティスだ。」
あなた「マクロってJinjaテンプレートのことですよね?SQLの中でプログラミングみたいなことができるんですか?」
田中VPoE「その通り。DRY原則を守りながら、再利用可能なSQL部品を作れるんだ。ただし、使いすぎると可読性が下がるから、バランスが大事だよ。」
Jinjaテンプレートとマクロ
Jinjaの基本構文
dbtではJinjaテンプレートエンジンを使って、SQLを動的に生成できます。
-- 変数の参照
{{ var('start_date', '2024-01-01') }}
-- 条件分岐
{% if target.name == 'prod' %}
-- 本番環境のみの処理
{% endif %}
-- ループ
{% for status in ['pending', 'completed', 'cancelled'] %}
SUM(CASE WHEN order_status = '{{ status }}' THEN 1 ELSE 0 END)
AS {{ status }}_count
{% if not loop.last %},{% endif %}
{% endfor %}
カスタムマクロの作成
-- macros/cents_to_dollars.sql
{% macro cents_to_dollars(column_name) %}
CAST({{ column_name }} AS NUMERIC) / 100
{% endmacro %}
-- models/staging/stg_payments.sql
SELECT
payment_id,
{{ cents_to_dollars('amount_cents') }} AS amount,
{{ cents_to_dollars('fee_cents') }} AS fee
FROM {{ source('raw', 'payments') }}
実用的なマクロ例
-- macros/generate_surrogate_key.sql
{% macro generate_surrogate_key(field_list) %}
TO_HEX(MD5(CONCAT(
{% for field in field_list %}
COALESCE(CAST({{ field }} AS STRING), '_null_')
{% if not loop.last %}, '|', {% endif %}
{% endfor %}
)))
{% endmacro %}
-- macros/date_spine.sql
{% macro date_spine(start_date, end_date) %}
SELECT date
FROM UNNEST(
GENERATE_DATE_ARRAY(
'{{ start_date }}',
'{{ end_date }}',
INTERVAL 1 DAY
)
) AS date
{% endmacro %}
パッケージ管理
packages.ymlの設定
# packages.yml
packages:
- package: dbt-labs/dbt_utils
version: [">=1.0.0", "<2.0.0"]
- package: dbt-labs/dbt_date
version: [">=0.7.0", "<1.0.0"]
- package: calogica/dbt_expectations
version: [">=0.8.0", "<1.0.0"]
- package: elementary-data/elementary
version: [">=0.13.0", "<1.0.0"]
# パッケージのインストール
dbt deps
主要パッケージ
| パッケージ | 用途 | 主要機能 |
|---|---|---|
| dbt_utils | ユーティリティ | surrogate_key, pivot, union_relations |
| dbt_expectations | データ品質テスト | Great Expectations風のテスト |
| dbt_date | 日付操作 | date_spine, fiscal_year |
| elementary | 可観測性 | 異常検知、テスト結果ダッシュボード |
| codegen | コード生成 | YAMLの自動生成 |
Hooks(フック)
特定のタイミングで追加SQLを実行します。
# dbt_project.yml
on-run-start:
- "CREATE SCHEMA IF NOT EXISTS {{ target.schema }}_audit"
on-run-end:
- "{{ log_run_results() }}"
models:
netshop:
marts:
+post-hook:
- "GRANT SELECT ON {{ this }} TO GROUP analysts"
| フック | 実行タイミング |
|---|---|
| on-run-start | dbt run の開始前 |
| on-run-end | dbt run の完了後 |
| pre-hook | モデルのビルド前 |
| post-hook | モデルのビルド後 |
セレクター構文
特定のモデルだけを実行する強力な構文です。
# 特定モデルとその上流すべて
dbt run --select +mart_customer_ltv
# 特定モデルとその下流すべて
dbt run --select stg_orders+
# タグ指定
dbt run --select tag:daily
# 変更があったモデルのみ(slim CI)
dbt run --select state:modified+ --state ./prod-manifest
| 構文 | 意味 |
|---|---|
model_name | 指定モデルのみ |
+model_name | 指定モデルとその上流すべて |
model_name+ | 指定モデルとその下流すべて |
+model_name+ | 上流と下流すべて |
tag:daily | タグがdailyのモデル |
path:models/marts | 特定パス配下 |
運用のベストプラクティス
1. 命名規則
| 要素 | 規則 | 例 |
|---|---|---|
| Stagingモデル | stg_{source}_{entity} | stg_mysql_orders |
| Intermediateモデル | int_{entity}_{verb} | int_orders_enriched |
| Martモデル | mart_{domain}_{entity} | mart_finance_daily_revenue |
| マクロ | {verb}_{noun} | generate_surrogate_key |
| テスト | assert_{condition} | assert_revenue_positive |
2. CI/CDパイプライン
# .github/workflows/dbt_ci.yml
name: dbt CI
on:
pull_request:
paths: ['models/**', 'macros/**', 'tests/**']
jobs:
dbt-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dbt
run: pip install dbt-bigquery
- name: dbt deps
run: dbt deps
- name: dbt build (modified only)
run: dbt build --select state:modified+ --state ./prod-manifest
3. 環境分離
# profiles.yml
netshop:
target: dev
outputs:
dev:
type: bigquery
project: netshop-dev
dataset: "dbt_{{ env_var('USER') }}"
prod:
type: bigquery
project: netshop-prod
dataset: analytics
まとめ
| 項目 | ポイント |
|---|---|
| Jinjaマクロ | SQL部品の再利用、DRY原則の適用 |
| パッケージ | dbt_utils等のコミュニティ資産を活用 |
| Hooks | 実行前後の追加処理(権限付与等) |
| セレクター | 部分実行、CI/CDでの差分ビルド |
| 運用 | 命名規則・CI/CD・環境分離が重要 |
チェックリスト
- Jinjaテンプレートの基本構文を理解している
- カスタムマクロを作成できる
- パッケージのインストールと利用方法を知っている
- セレクター構文を使って部分実行ができる
- CI/CDパイプラインの構成を理解している
次のステップへ
dbtの高度な機能を学びました。次は演習で、NetShop社のデータ変換パイプラインをdbtで実際に構築してみましょう。
推定読了時間:30分