dbt で生データを参照するとき、直接テーブル名を書かずに source() を使うのが定石です。これは見た目の書き方の違いだけでなく、環境可搬性、ドキュメント、リネージ、テスト、新鮮度管理まで効く重要な選択です。
本記事では、dbt の Sources 定義と source() 関数の基本、導入メリット、定義のベストプラクティス、テストと新鮮度チェック、運用パターン、資格試験で問われやすい観点をまとめます。
source() は、dbt で管理していない外部テーブル(生データなど)を参照するための Jinja 関数です。呼び出しは source('source_name', 'table_name') の2引数で、両者は YAML の sources 定義で宣言した name に一致します(実テーブル名が別名なら identifier でマッピングします)。
ref() は dbt がビルドするモデル間の依存を表現し、source() は外部データに対する依存を表現します。どちらも dbt のリネージグラフに反映され、コンパイル時に正しいデータベース・スキーマ・テーブル名へ解決されます。
リネージの全体像(Sources → Staging → Marts)
Sources を定義して source() で参照すると、環境に依存しない解決、リネージとドキュメントの自動生成、ソースレベルのテストと新鮮度チェック、権限や命名の差異吸収(identifier)が一度に得られます。これは実務でも試験でも最頻出の評価ポイントです。
特に新鮮度(freshness)は Sources 特有の機能で、最新取り込み時刻を監視し、遅延を早期検知できます。可観測性の起点として Sources をきちんと定義しておくのが中長期のコスト最適です。
| 手法 | 主目的 | 環境可搬性 | リネージ/ドキュメント |
|---|---|---|---|
| source() | 外部テーブル参照 | 高い(YAMLで解決) | 反映される(Sourcesとして) |
| ref() | dbtモデル参照 | 高い(アダプタが解決) | 反映される(モデルとして) |
| スキーマ直書き | 一時的な参照 | 低い(環境依存) | 反映されない |
Sources は schema.yml(version: 2)で宣言します。source: name は論理名、tables[].name は source() の第2引数で使う論理名です。実テーブル名が異なる場合は identifier を指定します。database や schema は target ごとに上書きされる戦略(env/vars)を併用することも多いです。
ベストプラクティスとして、raw のような着地点ごとに source を分け、説明やオーナー、読み取り権限の所在を docs ブロックや meta に明記します。外部システム起因の取り込み遅延がある場合は freshness の filter で当日分などに絞ると誤検知を避けられます。
| 設定項目 | 役割 | 注意点 |
|---|---|---|
| name / tables.name | source()/docs 上の論理名 | 実名と異なるなら identifier を併用 |
| identifier | 実テーブル名の指定 | 大文字小文字や記号の差異を吸収 |
| freshness | 新鮮度のしきい値 | filter で対象期間を絞ると実行負荷低減 |
YAML での Sources 定義と model からの参照例
# schema.yml(version: 2)
version: 2
sources:
- name: raw # 論理名(source() 第1引数)
database: {{ target.database }} # 環境ごとに解決
schema: raw
description: 原始取り込み領域(外部管理)
freshness:
warn_after: {count: 2, period: hour}
error_after: {count: 6, period: hour}
filter: "_ingested_at >= dateadd('day', -1, current_timestamp())"
tables:
- name: orders_raw # 論理名(source() 第2引数)
identifier: ext_orders # 実テーブル名
loaded_at_field: _ingested_at
columns:
- name: order_id
tests:
- not_null
-- models/stg_orders.sql
with src as (
select *
from {{ source('raw', 'orders_raw') }}
)
select
cast(order_id as string) as order_id,
customer_id,
order_ts,
_ingested_at
from src;ソース列への not_null や unique などの汎用テストは、通常のモデル列テストと同様に YAML で宣言します。dbt test を実行するとソース列も検証対象となり、異常は早期に検知されます。
新鮮度は dbt source freshness コマンドで評価されます。loaded_at_field を基準に現在時刻との差を測り、warn_after / error_after を超えた場合にステータスが変わります。filter を使い、派生ビューや長期履歴を除外して当日分のみを対象にするなど、実データの性質に合わせてチューニングします。
| 設定/コマンド | 機能 | 補足 |
|---|---|---|
| columns[].tests | ソース列の品質テスト | not_null, unique など |
| loaded_at_field | 新鮮度の基準列 | 取り込み時刻を指す列を指定 |
| dbt source freshness | 新鮮度評価の実行 | warn_after / error_after を判定 |
安定運用の基本は「Sources で外部を明示」「stg レイヤで整形」「上位は ref() で接続」の三層です。環境やアダプタ(Snowflake/Databricks/BigQuery 等)の差異は Sources と profiles.yml 側で解消し、モデルは常に source()/ref() で抽象化します。
避けるべきは、スキーマ直書きや、実テーブル名の変更を前提にした脆い依存です。identifier を使えば命名の差分を吸収でき、下流への影響を最小化できます。
| パターン | 利点 | リスク/注意点 |
|---|---|---|
| source() + stg 整形 | 可観測性と保守性が高い | 初期定義の手間はあるが長期的に得 |
| 直書き参照 | 短期の素早い試行 | 環境移行・監査・変更時に破綻 |
| identifier 併用 | 命名変更の影響を局所化 | 論理名と実名の混同に注意 |
試験では、source() と ref() の使い分け、Sources の YAML 定義(name と identifier の役割、loaded_at_field、freshness)が頻出です。また、環境可搬性やリネージに関する設問で、スキーマ直書きが不正解になるパターンが多い点に注意します。
コード断片の読み取りでは、source() の2引数が YAML の name を参照しているか、freshness の warn_after/error_after の意味、filter の影響範囲などを把握しているかが問われやすいです。
| キーワード | 出題意図 | 押さえどころ |
|---|---|---|
| source() | 外部参照の表現 | 2引数(source名, テーブル名)は YAML の name |
| freshness | 新鮮度監視 | warn_after / error_after / filter / loaded_at_field |
| identifier | 実テーブル名の差分吸収 | 論理名と実名の橋渡し |
Analytics Engineer
問題 1
BigQuery 上の raw データセットにある billing_transactions を dbt モデルから参照したい。テーブルは dbt で管理していない。正しい参照方法はどれか。
正解: A
外部テーブルは Sources に定義し、source() で参照するのが正解。これにより環境可搬性・リネージ・テスト・新鮮度が機能する。ref() は dbt 管理のモデル用。スキーマ直書きや変数連結ではリネージや新鮮度の恩恵が得られない。
source() と ref() を同じモデルで混在させてもよいですか?
はい。外部データの取り込み部分は source()、下流の内部依存は ref() でつなぎます。stg モデルで source() を使い、その後の marts/dim/fact では ref() を使うのが一般的です。
新鮮度チェックはビューや外部テーブルでも使えますか?
loaded_at_field が解決でき、アダプタ側で時刻比較が可能なら評価できます。取得コストが高い場合は filter で期間を絞るなど負荷対策を行ってください。
Snowflake や Databricks など異なる DWH でも設定は共通化できますか?
共通化できます。database/schema は profiles.yml や target 変数で切り替え、モデルは常に source()/ref() を用いることで可搬性を確保します。大文字小文字や命名差は identifier と quoting 設定で吸収します。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...