dbt

dbt ref() 関数: 依存解決とモデル間参照の実務

2026-04-19
NicheeLab編集部

ref() はdbtプロジェクトのDAGを形成し、モデルの実体名解決とビルド順序を自動化します。Analytics Engineer試験でも頻出の基本です。

本稿では、ref()の解決タイミング、マテリアライゼーションとの関係、クロスパッケージ参照、seedやsnapshotとの連携、アンチパターン、トラブルシューティングまでを一気通貫で確認します。

ref()の基礎: 参照とDAG依存解決

ref() はコンパイル時に、与えたモデル名を環境に応じた実体名(データベース.スキーマ.テーブル/ビュー)へ解決し、さらに依存関係グラフにエッジを追加します。これにより dbt build は上流から下流へ正しい順番で実行され、トランジティブな依存も自動で考慮されます。

実体名はtarget(プロファイルとターゲット)に基づき、アダプタ(Snowflake、BigQuery、Databricks SQLなど)の規則に沿って適切にクオート・命名されます。物理名をハードコードしないため、開発/本番でスキーマが異なってもSQLを共通化できます。

  • ref('model_name') は同一パッケージ内のモデルを参照
  • ref('package_name', 'model_name') で依存パッケージのモデルを参照
  • ref() を書いた時点でDAGに依存エッジが追加され、ビルド順序が保証される

ref() によるDAGのイメージ

 [seed_orders] ---> [stg_orders] ----> \
                       ^                 \
                       |                  > [fct_orders]
 [stg_payments] -------/                 /
 [dim_customers] ----------------------->/

基本例: fct_orders.sql

with orders as (
  select * from {{ ref('stg_orders') }}
),
payments as (
  select * from {{ ref('stg_payments') }}
)
select
  o.order_id,
  o.customer_id,
  sum(p.amount) as revenue
from orders o
left join payments p on p.order_id = o.order_id
group by 1,2;

コンパイル時の解決とマテリアライゼーションの違い

ref() はコンパイル段階で解決されます。tableやview、incrementalなどのマテリアライゼーションでは、ref() は対応するリレーション名に展開されます。一方、ephemeral モデルを参照した場合、dbtはそのSQLを下流モデル内のCTEとしてインライン展開します(物理テーブルは作られません)。

この動作により、上流SQLの再利用と最適化を両立できます。ただし、ephemeralの多用はCTEが肥大化し、コンパイル後SQLが複雑になる可能性があるため、クエリプランや実行時間を観察しつつ、テーブル化やインクリメンタル化の検討が必要です。

  • table/view/incremental: ref() → 実体名へ解決
  • ephemeral: ref() → CTEへインライン展開(実体は作られない)
  • コンパイル前に名称をハードコードしないため、環境ごとの差分に強い

ephemeralの例: インライン展開される補助モデル

-- models/utils_joined.sql
{{ config(materialized='ephemeral') }}
select *
from {{ ref('stg_orders') }} o
join {{ ref('stg_payments') }} p on p.order_id = o.order_id;

-- models/fct_orders.sql(上記を参照)
select order_id, customer_id, sum(amount) as revenue
from {{ ref('utils_joined') }}
group by 1,2;

クロスパッケージ参照と命名衝突の回避

パッケージ間でモデルを使い回す場合、packages.yml に依存関係を宣言し、ref('package_name', 'model_name') で明示的に参照します。これにより名前衝突を避け、どのパッケージのモデルかが一目で分かります。

同名モデルが存在する可能性がある大規模モノレポでは、クロスパッケージ参照を癖にするのが安全です。CIでは依存解決とキャッシュが正しく働くため、ビルドが安定します。

  • packages.ymlでバージョンをピン留めして再現性を担保
  • クロスパッケージ参照は2引数のrefを用いる
  • 曖昧さ回避のため、命名規約(接頭辞など)も併用するとよい

クロスパッケージrefの設定例

# packages.yml
packages:
  - package: org/payments_pkg
    version: 0.6.1

-- models/fct_orders.sql
select *
from {{ ref('core_pkg', 'stg_orders') }} o
left join {{ ref('payments_pkg', 'stg_payments') }} p
  on p.order_id = o.order_id;

seed・snapshot・testとの連携

ref() はモデルだけでなく seed や snapshot にも使えます。seedはCSVから作られる静的テーブル、snapshotはSCD管理用の履歴テーブルです。いずれもref()で参照することでDAGに組み込み、ビルド順序と命名解決の恩恵を受けられます。

生データの外部テーブルを参照する場合は source() を使います。sourceはデータウェアハウスに既存の生テーブルを宣言的に登録し、ドキュメント化と依存管理を可能にする仕組みです。

  • seed/snapshotはref()、外部ソースはsource()
  • singularテスト内でもref()が使える(失敗行を返すクエリ)
  • モデル名変更はrefの解決に影響するため、物理名だけ変える場合はaliasを使う

singularテストの例: revenueがNULLでないことを検証

-- tests/test_revenue_not_null.sql
select 1
from {{ ref('fct_orders') }}
where revenue is null
limit 1;

実務の落とし穴とパフォーマンス上の注意

スキーマやテーブル名のハードコードは最も多いアンチパターンです。環境差分に弱く、DAGにも載らないため順序保証が失われます。常にref() / source()を優先しましょう。

ephemeralの多段ネストはコンパイル後SQLを巨大化させます。クエリ計画の確認、重要中間結果のテーブル化(materialized='table' や incremental化)を検討してください。

  • 直接参照はやめてref()/source()へ統一
  • リネームはaliasで行い、モデル名は温存して参照を切らさない
  • 大量のephemeralは避け、境界でテーブル化
  • pre/post-hookでの直接参照もref()で置き換え可能か検討

悪例と良例

-- 悪例: 物理名のハードコード(環境移行で破綻)
select * from PROD_ANALYTICS.core.stg_orders;

-- 良例: ref()で環境依存を解消し、DAGに参加
select * from {{ ref('stg_orders') }};

コマンド・セレクタとトラブルシューティング

dbt build -s fct_orders を実行すると、ref() で到達可能な上流モデルが自動で先に実行されます。変更影響範囲だけを実行したい場合は state 比較のセレクタを使います。

典型的なエラーは Model not found for ref。モデル名のタイプミス、対象モデルがdisabled、パッケージ未導入、選択子で除外されている、などを確認します。

  • dbt list -s で選択結果を確認してから build
  • disabled: true のモデルはref解決対象外
  • クロスパッケージはpackages.ymlの導入とバージョンを確認
  • 物理名変更はalias、論理名変更は影響範囲の再配線が必要
対象依存グラフ登録環境可搬性代表的な用途
ref()あり(上流→下流の順序保証)高い(targetに応じて解決)モデル・seed・snapshot の参照
source()あり(ソース→モデル)中(宣言は環境依存しないが実体は外部)生データの既存テーブル参照
直接DB参照なし低い(環境や命名に固定)暫定調査や一時的な手動クエリ

選択子と実行例

# 依存込みで特定モデルのみを実行
$ dbt build -s fct_orders

# 変更のあった下流を実行(state比較)
$ dbt build -s state:modified+  --state path/to/artifacts

# 参照解決の確認
$ dbt list -s fct_orders --output name

問題で確認

Analytics Engineer

問題 1

モデル fct_orders が {{ ref('stg_orders') }} と {{ ref('stg_payments') }} を参照している。dbt build -s fct_orders を実行したときの挙動として正しいのはどれか。

  1. 上流の stg_orders と stg_payments が先に構築され、環境のスキーマに合わせた実体名に解決される
  2. ビルド順序の保証はなく、fct_ordersが先に実行される可能性がある
  3. 参照先は常にビューとして再作成されるため、マテリアライゼーション設定は無視される
  4. 開発と本番のいずれでも同一の物理名に解決される

正解: A

ref() はDAGに依存エッジを追加し、上流から順にビルドします。実体名の解決はtargetに依存するため、環境ごとに適切なスキーマ・データベースに展開されます。マテリアライゼーション設定は無視されません。

よくある質問

ref() と source() はどう使い分けるべきですか?

ref() はdbtが管理するモデル・seed・snapshotを参照する際に使います。source() はデータウェアハウス上の既存の生テーブルを宣言して参照するために使います。外部データの取り込み起点はsource()、その後の変換はref()でつなぐのが基本です。

モデル名を変更したいが、参照を壊したくありません。どうすればよいですか?

物理テーブル名だけ変えたい場合はモデルのaliasを使います(モデル名は据え置き)。これによりref('旧モデル名')は引き続き解決され、物理名だけが変更されます。論理モデル名自体を変える場合は、移行期間を設けて旧モデルを残すか、下流の参照先を一斉に置換してDAGを更新します。

クロスパッケージ参照でエラーになります。何を確認すべきですか?

packages.ymlで該当パッケージが導入されているか、バージョンが正しいかを確認し、ref('package_name', 'model_name') の2引数形式で記述しているかを見直してください。さらに、対象モデルがdisabledでないこと、選択子で除外されていないことも確認します。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
dbt

dbt Model の基礎: SQL で定義する変換の最小単位

Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...

dbt

dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点

dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...

dbt

dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点

dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...

dbt

dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト

Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....

dbt

dbt_project.yml の読み方:主要設定と命名を最短で掴む

dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...

dbtの記事一覧 (100件)
© 2026 NicheeLab All rights reserved.