の力は「model のレイヤ分け」から始まります。生データをそのまま分析クエリに使うとカオスになるので、staging → intermediate → marts の 3 層を意識的に組むのが標準パターン。dbt Labs 自身が推奨する best practice guide にも明記されています。
3 層レイヤモデル
| レイヤ | 目的 | 命名 | materialization |
|---|---|---|---|
| source | 外部データソースの宣言( は書かない、`schema.yml` のみ) | ソース名そのまま | — |
| staging | 1ソース1モデル、型変換とリネームだけ | `stg_<source>__<table>.sql` | view (ストレージ節約) |
| intermediate | 複数 stg を JOIN して中間テーブル | `int_<concept>__<verb>.sql` | ephemeral or view |
| marts | ビジネス指標の正本テーブル | `fct_<entity>.sql` / `dim_<entity>.sql` | table or incremental |
staging で型/命名を揃える → どの marts もここから ref() するので、上流の変更は staging だけ直せばよい。intermediate で重い JOIN を 1 度だけ → marts が読みやすくなる。marts は最終的な分析テーブル で、 ツールはここだけ参照。
ディレクトリ実例
models/├── staging/│ ├── stripe/│ │ ├── _stripe__sources.yml # source 宣言│ │ ├── _stripe__models.yml # stg model 定義 + tests│ │ ├── stg_stripe__charges.sql│ │ ├── stg_stripe__customers.sql│ │ └── stg_stripe__refunds.sql│ └── salesforce/│ └── ...├── intermediate/│ └── int_customer_revenue__monthly.sql└── marts/ ├── core/ │ ├── _core__models.yml │ ├── dim_customers.sql │ ├── fct_orders.sql │ └── fct_revenue_daily.sql └── finance/ └── fct_invoices.sqlStep 1: source の宣言
version: 2
sources: - name: stripe # source name (ref で 'stripe' と参照) database: raw # 物理 DB (BQ なら project) schema: stripe # 物理 schema (BQ なら dataset) description: "Stripe からの ELT 取込済み生データ" freshness: # 鮮度監視 (dbt source freshness で確認) warn_after: { count: 12, period: hour } error_after: { count: 24, period: hour } loaded_at_field: _loaded_at # 鮮度判定に使うカラム
tables: - name: charges description: "Stripe charge イベント" columns: - name: id tests: - unique - not_null - name: amount description: "金額 (最小通貨単位、JPY なら円)"
- name: customers description: "Stripe Customer オブジェクト"Step 2: staging モデル
{{ config(materialized='view') }}
WITH source AS ( SELECT * FROM {{ source('stripe', 'charges') }}),
renamed AS ( SELECT id AS charge_id, customer_id, amount, currency, status, created AS charged_at, refunded, _loaded_at FROM source WHERE status = 'succeeded' -- staging で軽いフィルタは OK)
SELECT * FROM renamed(1) 1 ファイル = 1 ソーステーブル。(2) `source()` を使う ── source 経由でないと dbt が依存を追えない。(3) rename と型変換だけ ── ビジネスロジックは marts で。(4) ほぼ view materialization で OK ── 直下のテーブルは軽量。
Step 3: marts モデル
{{ config(materialized='table') }}
WITH charges AS ( SELECT * FROM {{ ref('stg_stripe__charges') }}),
daily AS ( SELECT DATE(charged_at) AS date, currency, COUNT(DISTINCT customer_id) AS unique_customers, COUNT(*) AS charge_count, SUM(amount) AS gross_revenue, SUM(CASE WHEN refunded THEN amount ELSE 0 END) AS refund_amount, SUM(amount) - SUM(CASE WHEN refunded THEN amount ELSE 0 END) AS net_revenue FROM charges GROUP BY 1, 2)
SELECT * FROM dailyStep 4: 実行
# 全モデル実行dbt run
# 特定モデルだけdbt run --select fct_revenue_daily
# モデルとその依存元すべて (上流)dbt run --select +fct_revenue_daily
# モデルとその依存先すべて (下流)dbt run --select fct_revenue_daily+
# 親と子両方dbt run --select +fct_revenue_daily+
# テストdbt test --select fct_revenue_daily命名規則:dbt Labs 推奨
| プレフィックス | 意味 | 例 |
|---|---|---|
| `stg_<source>__<table>` | staging (1ソース1モデル) | `stg_stripe__charges` |
| `int_<concept>__<verb>` | intermediate (動詞含む) | `int_orders__pivoted` |
| `dim_<entity>` | dimension (主体) | `dim_customers` |
| `fct_<entity>` | fact (イベント・トランザクション) | `fct_orders` |
| `agg_<grain>` | 事前集計(任意) | `agg_revenue_daily` |
次の話
EP.04 では `ref()` と `source()` の挙動を深掘りし、依存グラフがどう作られるかを扱います。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。