0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Snowflake】dbt プロジェクトを作成するときのざっくり流れ

0
Posted at

はじめに

この記事では、Snowflake をデータウェアハウスとして使い、dbt(data build tool)プロジェクトをゼロから作成する手順を解説します。

この記事の対象読者

  • Snowflake は触ったことがあるが、dbt は初めて
  • dbt の公式ドキュメントを読む前に全体像を掴みたい
  • 手を動かしながら最初の一歩を踏み出したい

dbt × Snowflake の全体像

dbt は SELECT 文を書くだけで Snowflake 上にテーブルやビューを自動生成するデータ変換ツールです。ETL の「T(Transform)」を担い、バージョン管理・テスト・ドキュメントといったソフトウェアエンジニアリングのベストプラクティスをデータパイプラインに持ち込める点が最大の特長です。

全体のワークフローは以下のようになります。

[Snowflake 上の生データ]
       ↓ source() で参照
[dbt Model(SQL + Jinja)]
       ↓ dbt run で実行
[Snowflake 上のテーブル / ビュー]
       ↓ dbt test で検証
[データ品質が保証された分析用テーブル]

dbt プロジェクトを作成する3つの方法

Snowflake で dbt プロジェクトを始めるには、大きく3つの選択肢があります。

1. dbt Core(CLI)をローカルにインストールする方法

最もオーソドックスな方法です。Python の pip で dbt-snowflake アダプタをインストールし、dbt init コマンドでプロジェクトの雛形を生成します。VS Code などの好みのエディタで自由に開発でき、Git との連携も柔軟に構成できます。

2. dbt Cloud を使う方法

dbt Labs 社が提供する SaaS 環境です。ブラウザ上の IDE でモデルの編集・実行・テストが完結し、ジョブスケジューラや CI 機能も組み込まれています。Snowflake との接続設定は GUI 上で行うため、環境構築のハードルが低いのが特長です。無料の Developer プランもあります。

3. dbt Core + Docker で構築する方法

dbt Core の実行環境を Docker コンテナにまとめるアプローチです。dbt のバージョンや Python の依存関係を Dockerfile に固定できるため、「自分の環境では動くのに他の人では動かない」という問題を防げます。CI/CD パイプライン(GitHub Actions、GitLab CI など)への組み込みや、Airflow・Dagster などのオーケストレーションツールとの連携にも向いています。

本記事では、最も基本となる 1. dbt Core(CLI) の方法を詳しく解説していきます。


事前準備:Snowflake 側の環境構築

dbt を接続する前に、Snowflake 側に専用のリソースを作成しておきましょう。

dbt 用の Role・User・Warehouse・Database の作成

以下の SQL を Snowflake 上で ACCOUNTADMIN または SECURITYADMIN ロールで実行します。

Snowflake_setup.sql
-- ============================================
-- 1. dbt 用 Role の作成
-- ============================================
USE ROLE SECURITYADMIN;
CREATE ROLE IF NOT EXISTS TRANSFORM_ROLE;

-- SYSADMIN の配下に配置(推奨)
GRANT ROLE TRANSFORM_ROLE TO ROLE SYSADMIN;

-- ============================================
-- 2. dbt 用 User の作成
-- ============================================
CREATE USER IF NOT EXISTS DBT_USER
    PASSWORD = 'StrongPassword123!'  -- 後で変更してください
    DEFAULT_ROLE = TRANSFORM_ROLE
    DEFAULT_WAREHOUSE = TRANSFORM_WH;

GRANT ROLE TRANSFORM_ROLE TO USER DBT_USER;

-- ============================================
-- 3. dbt 用 Warehouse の作成
-- ============================================
USE ROLE SYSADMIN;
CREATE WAREHOUSE IF NOT EXISTS TRANSFORM_WH
    WAREHOUSE_SIZE = 'X-SMALL'
    AUTO_SUSPEND = 60          -- 60秒無操作で自動停止
    AUTO_RESUME = TRUE
    INITIALLY_SUSPENDED = TRUE;

-- ============================================
-- 4. Database / Schema の作成
-- ============================================
CREATE DATABASE IF NOT EXISTS ANALYTICS;
CREATE SCHEMA IF NOT EXISTS ANALYTICS.DEV;
CREATE SCHEMA IF NOT EXISTS ANALYTICS.PROD;

-- ============================================
-- 5. 権限の付与
-- ============================================
GRANT USAGE ON WAREHOUSE TRANSFORM_WH TO ROLE TRANSFORM_ROLE;
GRANT ALL ON DATABASE ANALYTICS TO ROLE TRANSFORM_ROLE;
GRANT ALL ON SCHEMA ANALYTICS.DEV TO ROLE TRANSFORM_ROLE;
GRANT ALL ON SCHEMA ANALYTICS.PROD TO ROLE TRANSFORM_ROLE;

-- ソースデータを読み取るための権限(例: RAW_DB に生データがある場合)
GRANT USAGE ON DATABASE RAW_DB TO ROLE TRANSFORM_ROLE;
GRANT USAGE ON SCHEMA RAW_DB.PUBLIC TO ROLE TRANSFORM_ROLE;
GRANT SELECT ON ALL TABLES IN SCHEMA RAW_DB.PUBLIC TO ROLE TRANSFORM_ROLE;
GRANT SELECT ON FUTURE TABLES IN SCHEMA RAW_DB.PUBLIC TO ROLE TRANSFORM_ROLE;

権限設定の考え方

dbt 用の権限設計では、次の2点を意識するのがポイントです。

最小権限の原則を守る — dbt は基本的に「ソースを SELECT して、ターゲットに CREATE/INSERT する」だけなので、不要な権限(ACCOUNTADMIN など)は付与しません。

環境ごとに分離するDEV スキーマと PROD スキーマを分け、開発時は DEV のみに書き込む運用にすると安全です。dbt の profiles.ymltarget を切り替えることで環境を制御できます。

上記の SQL はあくまで最小構成の例です。本番運用では、キーペア認証の導入やネットワークポリシーの設定も検討してください。


dbt Core(CLI)でプロジェクトを作成する手順

dbt-snowflake アダプタとは何か

dbt Core 自体は「SQL テンプレートを解釈してモデルの依存関係を管理するエンジン」です。特定のデータウェアハウスと通信する機能はアダプタが担います。Snowflake なら dbt-snowflake、BigQuery なら dbt-bigquery という具合です。

dbt-snowflake をインストールすると、以下のことが可能になります。

  • profiles.yml の情報をもとに Snowflake へのセッション確立
  • Materialization の種類に応じた Snowflake 方言の DDL/DML 自動生成CREATE OR REPLACE TABLEMERGE INTO など)
  • Snowflake 固有機能(Transient Table、クラスタリングキー、Secure View など)の dbt 設定からの利用

インストール手順

Python 3.9 以上が必要です。事前に python --version でバージョンを確認してください。

# 仮想環境の作成(推奨)
python -m venv dbt-env
source dbt-env/bin/activate  # Windows: dbt-env\Scripts\activate

# dbt-snowflake のインストール(dbt Core も一緒にインストールされる)
pip install dbt-snowflake

# インストール確認
dbt --version

実行結果の例:

Core:
  - installed: 1.9.x
  - latest:    1.9.x - Up to date!

Plugins:
  - snowflake: 1.9.x - Up to date!

dbt init によるプロジェクト雛形の生成

dbt init my_project

対話形式でいくつかの質問に回答します。

Which database would you like to use?
[1] snowflake

Enter a number: 1

account (https://<this_value>.snowflakecomputing.com): xy12345.ap-northeast-1.aws
user (dev username): DBT_USER
[1] password
[2] keypair
[3] sso
Desired authentication type option (enter a number): 1
password (dev password): ********
role (dev role): TRANSFORM_ROLE
warehouse (warehouse name): TRANSFORM_WH
database (database name): ANALYTICS
schema (schema name): DEV
threads (1 or more) [1]: 4

これにより、次の2つが生成されます。

生成されるもの パス 内容
プロジェクト ./my_project/ dbt_project.yml やモデル用ディレクトリ
プロファイル ~/.dbt/profiles.yml Snowflake への接続情報

profiles.yml の設定と接続確認

生成された profiles.yml は以下のような内容になります。

~/.dbt/profiles.yml
my_project:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: xy12345.ap-northeast-1.aws
      user: DBT_USER
      password: "{{ env_var('DBT_PASSWORD') }}"
      role: TRANSFORM_ROLE
      warehouse: TRANSFORM_WH
      database: ANALYTICS
      schema: DEV
      threads: 4
    prod:
      type: snowflake
      account: xy12345.ap-northeast-1.aws
      user: DBT_USER
      password: "{{ env_var('DBT_PASSWORD') }}"
      role: TRANSFORM_ROLE
      warehouse: TRANSFORM_WH
      database: ANALYTICS
      schema: PROD
      threads: 4

パスワードは env_var() で環境変数から読み込む形に変更しましょう。profiles.yml を Git にコミットしないよう .gitignore への追加も忘れずに。

接続が正しく設定されているかを確認するコマンドが dbt debug です。

cd my_project
export DBT_PASSWORD='StrongPassword123!'
dbt debug
成功時の出力
  profiles.yml file [OK found and valid]
  dbt_project.yml file [OK found and valid]
  ...
  Connection test: [OK connection ok]

All checks passed!

All checks passed! と表示されれば、Snowflake への接続は成功です。


プロジェクトのディレクトリ構成を理解する

dbt_project.yml の役割

プロジェクト全体の設定ファイルです。プロジェクト名、各ディレクトリのパス、モデルごとのデフォルト Materialization などを定義します。

dbt_project.yml
name: 'my_project'
version: '1.0.0'

profile: 'my_project'

model-paths: ["models"]
seed-paths: ["seeds"]
test-paths: ["tests"]
macro-paths: ["macros"]

models:
  my_project:
    staging:
      +materialized: view       # staging は軽量なビューで十分
    intermediate:
      +materialized: ephemeral  # 中間ロジックは CTE として展開
    marts:
      +materialized: table      # mart は実体テーブルとして作成

models / seeds / tests / macros の各ディレクトリ

dbt init で生成されるプロジェクトの初期構成は以下のとおりです。

my_project/
├── dbt_project.yml       # プロジェクト設定
├── packages.yml          # 外部パッケージの定義(手動作成)
├── models/               # SQL モデルを格納
│   └── example/          # サンプルモデル(削除OK)
├── seeds/                # CSV ファイルを格納
├── tests/                # Singular Test(カスタムテスト)を格納
├── macros/               # 再利用可能な Jinja マクロを格納
├── snapshots/            # SCD Type 2 用のスナップショット
└── analyses/             # 実行はしないが管理したい分析クエリ
ディレクトリ 内容
models/ プロジェクトの中心。1ファイル = 1モデル = Snowflake上の1テーブル or ビュー
seeds/ CSV をテーブル化する。マスタデータやマッピング定義など小規模データ向け
tests/ 自由な SQL で書くカスタムテスト。結果が0行なら成功
macros/ Jinja で書く再利用可能な関数。DRY 原則を実現
snapshots/ テーブルの変更履歴を自動追跡(SCD Type 2)
analyses/ dbt compile で SQL を展開するが、実行はしない分析クエリ

staging / intermediate / mart のレイヤー設計

models/ ディレクトリ内は、データの変換段階に応じたレイヤーで整理するのがベストプラクティスです。

models/
├── staging/          # ソースデータの薄い整形
│   ├── _sources.yml
│   ├── _stg_models.yml
│   ├── stg_raw__orders.sql
│   └── stg_raw__customers.sql
├── intermediate/     # 中間的なビジネスロジック
│   └── int_orders__joined.sql
└── marts/            # 分析用の最終テーブル
    ├── _mart_models.yml
    ├── fct_orders.sql
    └── dim_customers.sql
レイヤー 役割 Materialization 命名規則
staging ソースから取り込み、リネーム・型変換など最小限の整形を行う view stg_<source>__<table>
intermediate staging を組み合わせた中間ロジック ephemeral int_<entity>__<verb>
mart BIツールやアナリストが直接参照する最終テーブル table fct_<entity> / dim_<entity>

fct_ はファクト(イベント・トランザクション)、dim_ はディメンション(マスタ・属性)を表す、ディメンショナルモデリングの命名慣習です。


最初のモデルを作成して実行する

ここからは、実際に手を動かしてモデルを作っていきます。models/example/ ディレクトリは不要なので削除してください。

以下の例では、Snowflake の RAW_DB.PUBLIC スキーマに ORDERS テーブルと CUSTOMERS テーブルが存在する前提で進めます。

source の定義(_sources.yml)

まず、dbt の管理外にある生データ(ソーステーブル)を宣言します。

models/staging/_sources.yml
version: 2

sources:
  - name: raw
    description: "EL ツールで取り込まれた生データ"
    database: RAW_DB
    schema: PUBLIC
    tables:
      - name: orders
        description: "受注データ"
        columns:
          - name: id
            description: "注文ID"
          - name: user_id
            description: "顧客ID"
          - name: status
            description: "注文ステータス"
          - name: amount
            description: "注文金額(セント)"
          - name: created_at
            description: "注文日時"
      - name: customers
        description: "顧客マスタ"
        columns:
          - name: id
            description: "顧客ID"
          - name: first_name
            description: "名"
          - name: last_name
            description: "姓"

staging モデルの作成

ソースデータに対して、カラム名の統一やリネームなど最小限の整形を行います。

models/staging/stg_raw__orders.sql
with source as (
    select * from {{ source('raw', 'orders') }}
),

renamed as (
    select
        id          as order_id,
        user_id     as customer_id,
        status,
        amount      as amount_cents,
        created_at  as ordered_at
    from source
)

select * from renamed
models/staging/stg_raw__customers.sql
with source as (
    select * from {{ source('raw', 'customers') }}
),

renamed as (
    select
        id          as customer_id,
        first_name,
        last_name,
        first_name || ' ' || last_name as full_name
    from source
)

select * from renamed

staging モデルでは 1ソーステーブル = 1モデル とし、ビジネスロジック(結合・集計など)は持ち込まないのが原則です。ここではカラムのリネームと簡単な加工のみに留めています。

mart モデルの作成(ref 関数による依存関係)

staging モデルを ref() 関数で参照し、ビジネス上の意味を持つ分析用テーブルを作成します。

models/marts/fct_orders.sql
with orders as (
    select * from {{ ref('stg_raw__orders') }}
),

customers as (
    select * from {{ ref('stg_raw__customers') }}
),

final as (
    select
        o.order_id,
        o.customer_id,
        c.full_name         as customer_name,
        o.status,
        (o.amount_cents / 100)::numeric(16,2) as amount_dollars,
        o.ordered_at
    from orders o
    left join customers c
        on o.customer_id = c.customer_id
)

select * from final
models/marts/dim_customers.sql
with customers as (
    select * from {{ ref('stg_raw__customers') }}
),

orders as (
    select * from {{ ref('stg_raw__orders') }}
),

customer_orders as (
    select
        customer_id,
        count(*)            as order_count,
        sum(amount_cents)   as total_amount_cents,
        min(ordered_at)     as first_order_at,
        max(ordered_at)     as last_order_at
    from orders
    group by customer_id
),

final as (
    select
        c.customer_id,
        c.full_name,
        coalesce(co.order_count, 0)          as order_count,
        coalesce(co.total_amount_cents, 0)   as total_amount_cents,
        co.first_order_at,
        co.last_order_at
    from customers c
    left join customer_orders co
        on c.customer_id = co.customer_id
)

select * from final

ref() を使わずに ANALYTICS.DEV.STG_RAW__ORDERS のように直接テーブル名を書くと、dbt が依存関係を認識できず、実行順序やリネージが壊れます。モデル間の参照には 必ず ref() を使いましょう

dbt run でモデルを実行する

dbt run
実行結果
Running with dbt=1.9.x
Found 4 models, 2 sources

Concurrency: 4 threads (target='dev')

1 of 4 START sql view model DEV.stg_raw__orders ........................ [RUN]
2 of 4 START sql view model DEV.stg_raw__customers ..................... [RUN]
1 of 4 OK created sql view model DEV.stg_raw__orders .................. [SUCCESS in 0.82s]
2 of 4 OK created sql view model DEV.stg_raw__customers ............... [SUCCESS in 0.91s]
3 of 4 START sql table model DEV.fct_orders ........................... [RUN]
4 of 4 START sql table model DEV.dim_customers ........................ [RUN]
3 of 4 OK created sql table model DEV.fct_orders ...................... [SUCCESS in 1.24s]
4 of 4 OK created sql table model DEV.dim_customers ................... [SUCCESS in 1.31s]

Finished running 2 view models, 2 table models in 0 hours 0 minutes and 4.52 seconds (4.52s).

Completed successfully

Done. PASS=4 WARN=0 ERROR=0 SKIP=0 TOTAL=4

staging モデル(view)が先に実行され、その後に mart モデル(table)が実行されています。これは ref() で定義した依存関係に基づいて dbt が DAG を構築し、正しい順序で実行してくれているためです。

Snowflake 上で結果を確認する

Snowflake のワークシートやクライアントツールから、作成されたオブジェクトを確認してみましょう。

-- 作成されたオブジェクトの一覧
SHOW OBJECTS IN SCHEMA ANALYTICS.DEV;

-- fct_orders の中身を確認
SELECT * FROM ANALYTICS.DEV.FCT_ORDERS LIMIT 10;

-- dim_customers の中身を確認
SELECT * FROM ANALYTICS.DEV.DIM_CUSTOMERS LIMIT 10;

staging レイヤーが VIEW として、mart レイヤーが TABLE として作成されていることが確認できるはずです。


テストとドキュメントを追加する

モデルが動くようになったら、次はデータ品質の検証とドキュメント整備を行います。

schema.yml にテストと description を記述する

models/staging/_stg_models.yml
version: 2

models:
  - name: stg_raw__orders
    description: "受注データの staging モデル。カラムのリネームのみ実施。"
    columns:
      - name: order_id
        description: "注文を一意に識別するID"
        tests:
          - unique
          - not_null
      - name: customer_id
        description: "注文した顧客のID"
        tests:
          - not_null
      - name: status
        description: "注文ステータス"
        tests:
          - not_null
          - accepted_values:
              values: ['placed', 'shipped', 'completed', 'returned']
      - name: amount_cents
        description: "注文金額(セント単位)"
      - name: ordered_at
        description: "注文日時"
        tests:
          - not_null

  - name: stg_raw__customers
    description: "顧客マスタの staging モデル。カラムのリネームとフルネーム生成を実施。"
    columns:
      - name: customer_id
        description: "顧客を一意に識別するID"
        tests:
          - unique
          - not_null
      - name: full_name
        description: "顧客のフルネーム(first_name + last_name)"
models/marts/_mart_models.yml
version: 2

models:
  - name: fct_orders
    description: "注文ファクトテーブル。顧客情報を結合済み。"
    columns:
      - name: order_id
        description: "注文ID"
        tests:
          - unique
          - not_null
      - name: customer_id
        description: "顧客ID"
        tests:
          - not_null
          - relationships:
              to: ref('dim_customers')
              field: customer_id

  - name: dim_customers
    description: "顧客ディメンションテーブル。注文集計情報を付与。"
    columns:
      - name: customer_id
        description: "顧客ID"
        tests:
          - unique
          - not_null
      - name: order_count
        description: "累計注文回数"

dbt に組み込まれている Generic Test(汎用テスト) は4種類あります。

テスト 検証内容
unique 値が一意であること
not_null NULL が存在しないこと
accepted_values 指定した値のいずれかであること
relationships 他テーブルのカラムに値が存在すること(参照整合性)

dbt test でデータ品質を検証する

dbt test
実行結果
Running with dbt=1.9.x
Found 4 models, 11 tests, 2 sources

Concurrency: 4 threads (target='dev')

1 of 11 START test accepted_values_stg_raw__orders_status__placed__shipped__completed__returned  [RUN]
2 of 11 START test not_null_stg_raw__orders_order_id ..................... [RUN]
...
11 of 11 START test unique_dim_customers_customer_id ..................... [RUN]

Finished running 11 tests in 0 hours 0 minutes and 3.21 seconds (3.21s).

Completed successfully

Done. PASS=11 WARN=0 ERROR=0 SKIP=0 TOTAL=11

dbt build を使えば dbt run + dbt test依存順にまとめて実行 できます。日常的にはこちらの方が便利です。

テストが失敗した場合、該当レコードの情報とともにエラーが表示されます。CI/CD に組み込むことで、PR マージ前にデータ品質を自動チェックする運用も実現できます。

dbt docs generate / serve でドキュメントを確認する

# ドキュメント用の JSON を生成
dbt docs generate

# ローカルサーバーを起動してブラウザで確認
dbt docs serve

ブラウザが自動で開き、以下の情報を閲覧できるドキュメントサイトが立ち上がります。

  • モデル一覧 — 各モデルの description、カラム定義、適用テスト
  • リネージグラフ — source → staging → mart のデータの流れを視覚的に確認
  • ソース情報 — 生データテーブルの定義と鮮度情報

リネージグラフは画面右下のアイコンから表示でき、影響範囲の把握やデバッグに非常に役立ちます。


Snowflake 固有の設定を活用する

dbt-snowflake アダプタには、Snowflake ならではの機能を dbt の設定から制御できる仕組みが用意されています。

Transient Table でストレージコストを削減する

Snowflake の Transient Table は、通常のテーブルと異なり Fail-safe 期間(7日間のデータ保護)がないテーブルです。再作成が容易な中間テーブルに適しています。

dbt_project.yml
models:
  my_project:
    intermediate:
      +materialized: table
      +transient: true    # Fail-safe なしでコスト削減
    marts:
      +materialized: table
      +transient: false   # mart は通常テーブルで保護

クラスタリングキーでクエリ性能を最適化する

大規模なテーブルに対して、よく使われるフィルタ条件のカラムをクラスタリングキーに設定すると、Snowflake のマイクロパーティションが最適化されクエリ性能が向上します。

models/marts/fct_orders.sql
{{
    config(
        materialized='table',
        cluster_by=['ordered_at']
    )
}}

select
    ...

クラスタリングキーは数億行以上の大規模テーブルで効果を発揮します。小規模テーブルに設定してもコストだけかかるため、テーブルサイズに応じて判断してください。

Query Tag で dbt 発行クエリを識別する

Snowflake の QUERY_HISTORY ビューで dbt が発行したクエリを識別しやすくするために、Query Tag を設定できます。

dbt_project.yml
models:
  my_project:
    +query_tag: 'dbt_run'

この設定により、Snowflake 側で以下のようなクエリで dbt の実行履歴だけを絞り込めます。

SELECT *
FROM TABLE(INFORMATION_SCHEMA.QUERY_HISTORY())
WHERE QUERY_TAG = 'dbt_run'
ORDER BY START_TIME DESC;

おわりに

以上で、Snowflake 上に dbt プロジェクトを作成し、モデルの実行・テスト・ドキュメント確認までの一通りの流れを体験できました。

最終的なプロジェクト構成

my_project/
├── dbt_project.yml
├── models/
│   ├── staging/
│   │   ├── _sources.yml
│   │   ├── _stg_models.yml
│   │   ├── stg_raw__orders.sql
│   │   └── stg_raw__customers.sql
│   └── marts/
│       ├── _mart_models.yml
│       ├── fct_orders.sql
│       └── dim_customers.sql
├── seeds/
├── tests/
├── macros/
└── snapshots/

参考リンク

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?