0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【dbt入門】3つの基本機能(seed/ref/source)の使い方についてまとめてみた

0
Last updated at Posted at 2025-11-24

はじめに

dbt (data build tool) の学習を始めましたが、dbt特有の機能について理解不足な部分があると感じたため、本記事ではdbt特有の seed機能ref機能source機能についてまとめてみました。

この記事で学べること

  • seed機能の使い方と実用例
  • ref機能の仕組みと重要性
  • source機能の仕組みと重要性

想定読者

  • SQLの基本的な知識がある方
  • dbtを学び始めたばかりの方

1. dbtとは?

1.1 dbtの位置づけ

dbt (data build tool) は、データウェアハウス内でのデータ変換を SQL で行うためのツールです。

**ELTにおけるT(tらんsfろ## はじめに

dbt (data build tool) の学習を始めましたが、dbt特有の機能について理解不足な部分があると感じたため、本記事ではdbt特有の seed機能ref機能source機能についてまとめてみました。

この記事で学べること

  • seed機能の使い方と実用例
  • ref機能の仕組みと重要性
  • source機能の仕組みと重要性

想定読者

  • SQLの基本的な知識がある方
  • dbtを学び始めたばかりの方

1. dbtとは?

1.1 dbtの位置づけ

dbt (data build tool) は、データウェアハウス内でのデータ変換を SQL で行うためのツールです。

また、ELTアーキテクチャが前提のツールであり、以下のようなデータパイプラインでの利用が想定されている。

生データ(RDS、S3など)
    ↓
データウェアハウス(BigQuery、Snowflake、Redshiftなど)
    ↓
【dbt】← ここでSQLを使ってデータ変換
    ↓
分析用データ(クリーンで使いやすいテーブル)

1.2 従来のSQLとの違い

項目 従来のSQL dbt
実行方法 クエリツールで手動実行 dbt runコマンド一つで実行
依存関係管理 手動で順序を管理 自動で依存関係を解決
テーブル参照 テーブル名を直接指定 ref()関数で参照
テスト 手動確認 自動テスト機能あり
バージョン管理 困難 Gitで簡単に管理

2. seed機能:「CSVをアップロードするだけ?」

2.1 seed機能とは

seed機能は、CSVファイルをデータウェアハウスのテーブルとして読み込む機能です。

# seedファイルの配置場所
project/
  ├── dbt_project.yml
  └── seeds/
      └── country_codes.csv  # ← CSVファイルをここに配置
# CSVをテーブルとして読み込む
dbt seed

2.2 seed機能のメリット

メリット1:バージョン管理が容易

従来の方法の問題点

  • SQLファイルを手動で管理
  • 変更履歴が追いにくい
  • チーム共有が困難

seedを使った場合

seeds/country_codes.csv
↓ Gitで管理
↓ チーム全体で同じデータを共有
↓ 変更履歴を自動追跡

メリット2:データの更新が簡単

従来の方法

-- データ更新のたびにDELETE→INSERT
DELETE FROM country_codes WHERE code = 'JP';
INSERT INTO country_codes VALUES ('JP', '日本');

seedを使った場合

# CSVファイルを編集
# コマンド一つで更新完了
dbt seed --full-refresh

メリット3:環境間でのデータ同期

従来の方法の問題点

開発環境と本番環境でマスタデータが異なる
→ 手動で同期する必要がある
→ 同期漏れのリスク

seedを使った場合

# 同じCSVファイルを使って、どの環境でも同じテーブルを作成
dbt seed  # 開発環境
dbt seed --target prod  # 本番環境

2.3 seed機能の実用例

例1:国コードマスタ

seeds/country_codes.csv

code,name,region
JP,Japan,Asia
US,United States,North America
CN,China,Asia
DE,Germany,Europe

seedの実行

dbt seed

結果

-- データウェアハウスに自動的にテーブルが作成される
SELECT * FROM country_codes;

-- code | name          | region
-- -----|---------------|---------------
-- JP   | Japan         | Asia
-- US   | United States | North America
-- CN   | China         | Asia
-- DE   | Germany       | Europe

例2:カテゴリマスタ

seeds/product_categories.csv

category_id,category_name,parent_category_id
1,Electronics,NULL
2,Computers,1
3,Smartphones,1
4,Laptops,2
5,Tablets,2

seedの実行

dbt seed

dbtモデルでの利用

-- models/products_with_category.sql
SELECT
    p.product_id,
    p.product_name,
    c.category_name,
    c.parent_category_id
FROM {{ ref('products') }} AS p
LEFT JOIN {{ ref('product_categories') }} AS c
    ON p.category_id = c.category_id

ここで登場した {{ ref() }} については、次の章で詳しく解説します。

2.4 seedの注意点

注意点1:大きなデータには向かない

推奨

  • マスタデータ(国コード、カテゴリなど)
  • 設定ファイル的なデータ
  • テスト用のサンプルデータ

非推奨

  • トランザクションデータ(大量の売上データなど)
  • 頻繁に更新されるデータ

seedは数千行程度のデータに適しています。数万行を超える場合は、別の方法(ELTツールなど)を検討しましょう。

3. ref機能:「なぜテーブル名を直接書かないの?」

3.1 ref機能とは

ref機能は、dbtモデル間で テーブルを参照する ための関数です。

従来のSQL

-- テーブル名を直接指定
SELECT * FROM my_database.my_schema.users;

dbtのref関数

-- ref関数で参照
SELECT * FROM {{ ref('users') }};

ref関数の基本的な使い方

1. 基本構文

{{ ref('モデル名') }}

2. 実際の使用例

-- models/stg_users.sqlというファイルがある場合
SELECT * FROM {{ ref('stg_users') }}

-- JOINでの使用
SELECT 
    u.user_id,
    o.order_id
FROM {{ ref('stg_users') }} AS u
LEFT JOIN {{ ref('stg_orders') }} AS o
    ON u.user_id = o.user_id

3. ref関数が実際に生成するSQL

-- dbt run実行時に以下のように変換される
SELECT * FROM dev_database.dbt_schema.stg_users

-- 本番環境では
SELECT * FROM prod_database.dbt_schema.stg_users

重要なポイント

  • ref()の引数はファイル名(拡張子なし)
  • データベース名やスキーマ名は不要
  • 環境ごとの設定はdbt_project.ymlで管理

3.2 ref機能のメリット

メリット1:依存関係の自動管理

従来のSQLの問題点

-- ステップ1:usersテーブルを作成
CREATE TABLE users AS
SELECT * FROM raw_users;

-- ステップ2:ordersテーブルを作成
CREATE TABLE orders AS
SELECT * FROM raw_orders;

-- ステップ3:user_ordersを作成(usersとordersに依存)
CREATE TABLE user_orders AS
SELECT
    u.user_id,
    u.name,
    o.order_id,
    o.amount
FROM users AS u
LEFT JOIN orders AS o
    ON u.user_id = o.user_id;

問題

  • 実行順序を 手動で管理 する必要がある
  • 順序を間違えるとエラーが発生
  • テーブルが増えると管理が困難

dbtのref関数を使った場合

models/staging/stg_users.sql

SELECT * FROM {{ source('raw_data', 'users') }}

models/staging/stg_orders.sql

SELECT * FROM {{ source('raw_data', 'orders') }}

models/marts/user_orders.sql

SELECT
    u.user_id,
    u.name,
    o.order_id,
    o.amount
FROM {{ ref('stg_users') }} AS u
LEFT JOIN {{ ref('stg_orders') }} AS o
    ON u.user_id = o.user_id

実行

# dbtが自動的に依存関係を解決して、正しい順序で実行
dbt run

# 実行順序(自動)
# 1. stg_users
# 2. stg_orders
# 3. user_orders

依存関係の可視化

# 依存関係グラフを生成
dbt docs generate
dbt docs serve

メリット2:環境に依存しない参照

従来のSQLの問題点

-- 開発環境
SELECT * FROM dev_database.dev_schema.users;

-- 本番環境(手動で書き換えが必要)
SELECT * FROM prod_database.prod_schema.users;

dbtのref関数

-- どの環境でも同じコード
SELECT * FROM {{ ref('users') }};

-- dbt_project.ymlで環境ごとの設定を管理
# 開発環境: dev_database.dev_schema.users
# 本番環境: prod_database.prod_schema.users

メリット3:リネームや移動に強い

従来のSQLの問題点

-- usersテーブルを参照している箇所が10箇所ある
SELECT * FROM users;  -- 箇所1
SELECT * FROM users;  -- 箇所2
...

-- usersをstg_usersにリネームした場合
-- 10箇所すべてを手動で修正する必要がある

dbtのref関数

-- すべての箇所でref関数を使用
SELECT * FROM {{ ref('users') }};

-- ファイル名を変更するだけで、参照は自動で更新される
# models/users.sql  models/stg_users.sql

3.3 ref機能の注意点

注意点1:循環参照は不可

-- models/table_a.sql
SELECT * FROM {{ ref('table_b') }}

-- models/table_b.sql
SELECT * FROM {{ ref('table_a') }}

-- エラー:循環参照
-- Error: Cycle detected: table_a -> table_b -> table_a

注意点2:ファイル名とモデル名の一致

ファイル名: stg_users.sql
ref関数: {{ ref('stg_users') }}

# ファイル名をそのまま使用(拡張子なし)

注意点3:source関数との使い分け

関数 用途
source() 生データ(外部テーブル)を参照 {{ source('raw_data', 'users') }}
ref() dbtモデル(自分で作成したテーブル)を参照 {{ ref('stg_users') }}

4. source機能:「生データをどう参照するの?」

4.1 source機能とは

source機能は、データウェアハウスに既に存在する 生データ(外部テーブル) を参照するための機能です。

【データの流れ】
生データ(外部から投入)
    ↓ source()で参照
ステージング(dbtで作成)
    ↓ ref()で参照
マート(dbtで作成)

4.2 最初の疑問:「テーブル名を直接書けば良くない?」

疑問

-- 普通にテーブル名を書けば良いのでは?
SELECT * FROM raw_database.raw_schema.users;

しかし、source関数を使うことで 多くのメリット が得られます。

4.3 source機能のメリット

メリット1:データソースの一元管理

従来の方法の問題点

-- models/stg_users.sql
SELECT * FROM raw_database.public.users;

-- models/stg_orders.sql
SELECT * FROM raw_database.public.orders;

-- models/stg_products.sql
SELECT * FROM raw_database.public.products;

-- データベース名を変更したい場合
-- → すべてのファイルを手動で修正する必要がある

sourceを使った場合

# models/sources.yml(一箇所で管理)
version: 2

sources:
  - name: raw_data
    database: raw_database
    schema: public
    tables:
      - name: users
      - name: orders
      - name: products
-- models/stg_users.sql
SELECT * FROM {{ source('raw_data', 'users') }}

-- models/stg_orders.sql
SELECT * FROM {{ source('raw_data', 'orders') }}

-- models/stg_products.sql
SELECT * FROM {{ source('raw_data', 'products') }}

-- データベース名の変更は sources.yml の1箇所を修正するだけ

メリット2:データ鮮度チェック

sourceにはfreshnessチェック機能があります

# models/sources.yml
version: 2

sources:
  - name: raw_data
    database: raw_database
    schema: public
    tables:
      - name: users
        loaded_at_field: updated_at  # 更新日時のカラム
        freshness:
          warn_after: {count: 12, period: hour}  # 12時間でwarning
          error_after: {count: 24, period: hour} # 24時間でerror
# データの鮮度をチェック
dbt source freshness

# 結果例
# WARN: Source raw_data.users is 13 hours old

メリット3:ドキュメント生成

# models/sources.yml
version: 2

sources:
  - name: raw_data
    description: "EC サイトの生データ"
    database: raw_database
    schema: public
    tables:
      - name: users
        description: "ユーザーマスタテーブル"
        columns:
          - name: user_id
            description: "ユーザーID(主キー)"
          - name: email
            description: "メールアドレス"
      - name: orders
        description: "注文トランザクションテーブル"
# ドキュメントを生成
dbt docs generate
dbt docs serve

# → ブラウザで生データの定義を確認できる

4.4 source機能の基本的な使い方

ステップ1:sources.ymlの作成

models/sources.yml

version: 2

sources:
  - name: raw_data          # sourceの名前(任意)
    database: raw_database  # データベース名
    schema: public          # スキーマ名
    tables:
      - name: users         # テーブル名
      - name: orders
      - name: products

ステップ2:モデルでsource関数を使用

models/staging/stg_users.sql

SELECT
    user_id,
    LOWER(email) AS email,
    created_at
FROM {{ source('raw_data', 'users') }}
WHERE deleted_at IS NULL

基本構文

{{ source('source名', 'テーブル名') }}

ステップ3:実行と確認

# モデルを実行
dbt run

# 生成されるSQL
# SELECT ... FROM raw_database.public.users WHERE deleted_at IS NULL

4.5 ref関数とsource関数の使い分け

使い分けの基本ルール

【データの流れ】

外部データ投入
    ↓
生テーブル(raw_users)
    ↓ ← source('raw_data', 'users')
stg_users(dbtモデル)
    ↓ ← ref('stg_users')
user_summary(dbtモデル)
    ↓ ← ref('user_summary')
最終テーブル(dbtモデル)

明確な基準

データの種類 使う関数
dbt外部からのデータ source() ETLツールで投入したデータ
dbtで作成したテーブル ref() dbtモデルで生成したテーブル
seedで作成したテーブル ref() CSVから作成したマスタ

4.6 source機能の注意点

注意点1:テーブルが存在しない場合

# エラー例
Error: Database Error in source raw_data.users
  relation "raw_database.public.users" does not exist

対処法

  • sources.ymlの設定を確認
  • データベースに実際にテーブルが存在するか確認
  • スキーマ名、テーブル名のスペルミスを確認

注意点2:複数のデータベースを扱う場合

# models/sources.yml
version: 2

sources:
  # EC サイトのデータ
  - name: ecommerce_data
    database: ecommerce_db
    schema: public
    tables:
      - name: users
      - name: orders

  # CRM システムのデータ
  - name: crm_data
    database: crm_db
    schema: public
    tables:
      - name: customers
      - name: contacts
-- 異なるsourceからのデータを結合
SELECT
    e.user_id,
    e.email,
    c.customer_type
FROM {{ source('ecommerce_data', 'users') }} AS e
LEFT JOIN {{ source('crm_data', 'customers') }} AS c
    ON e.email = c.email

4.7 実践的なsources.ymlの書き方

完全な設定例

version: 2

sources:
  - name: raw_data
    description: "EC サイトの生データ(毎日ETLで更新)"
    database: raw_database
    schema: public
    
    tables:
      - name: raw_users
        description: "ユーザーマスタの生データ"
        loaded_at_field: updated_at
        freshness:
          warn_after: {count: 12, period: hour}
          error_after: {count: 24, period: hour}
        columns:
          - name: user_id
            description: "ユーザーID(主キー)"
            tests:
              - unique
              - not_null
          - name: email
            description: "メールアドレス"
            tests:
              - not_null
          - name: created_at
            description: "作成日時"
          - name: updated_at
            description: "更新日時"
          - name: deleted_at
            description: "削除日時(論理削除)"
      
      - name: raw_orders
        description: "注文トランザクションの生データ"
        loaded_at_field: created_at
        freshness:
          warn_after: {count: 6, period: hour}
          error_after: {count: 12, period: hour}
        columns:
          - name: order_id
            description: "注文ID(主キー)"
            tests:
              - unique
              - not_null
          - name: user_id
            description: "ユーザーID(外部キー)"
            tests:
              - not_null
          - name: amount
            description: "注文金額"
          - name: status
            description: "注文ステータス"

5. まとめ

5.1 seed機能のポイント

使うべき場面

  • マスタデータの管理(国コード、カテゴリなど)
  • 環境間でのデータ同期
  • チームでのデータ共有

メリット

  • バージョン管理が容易(Git管理)
  • 更新が簡単(CSVを編集するだけ)
  • 環境間の同期が確実

注意点

  • 大量データには不向き(数千行程度まで)
  • 文字コードはUTF-8

5.2 ref機能のポイント

使うべき理由

  • 依存関係の自動管理
  • 環境に依存しない参照
  • メンテナンス性の向上

重要な特徴

  • 実行順序を自動解決
  • テーブル名の変更に強い
  • 依存関係の可視化が可能

基本的な使い方

  • ファイル名(拡張子なし)を引数に指定
  • dbtで作成したテーブル・seedを参照
  • JOINでも通常のテーブルと同様に使用

5.3 source機能のポイント

使うべき理由

  • 外部データソースの一元管理
  • データ鮮度チェック機能
  • ドキュメント自動生成

重要な特徴

  • sources.ymlで定義を一元管理
  • データベース名の変更に強い
  • テスト・ドキュメント機能との統合

基本的な使い方

  • sources.ymlで定義
  • dbt外部から投入されたデータを参照
  • ステージング層での使用が推奨

5.4 3つの機能の使い分けまとめ

機能 データの種類 使用場面
seed 静的マスタデータ 手動管理する小規模データ 国コード、カテゴリ、地域マスタ
source 外部投入データ dbt外部から投入される生データ ETLで取得したトランザクションデータ
ref dbt作成データ dbtで変換・集計したテーブル ステージング、マート、集計テーブル
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?