はじめに
この記事では、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 ロールで実行します。
-- ============================================
-- 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.yml で target を切り替えることで環境を制御できます。
上記の 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 TABLE、MERGE 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 は以下のような内容になります。
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 などを定義します。
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 の管理外にある生データ(ソーステーブル)を宣言します。
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 モデルの作成
ソースデータに対して、カラム名の統一やリネームなど最小限の整形を行います。
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
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() 関数で参照し、ビジネス上の意味を持つ分析用テーブルを作成します。
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
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 を記述する
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)"
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日間のデータ保護)がないテーブルです。再作成が容易な中間テーブルに適しています。
models:
my_project:
intermediate:
+materialized: table
+transient: true # Fail-safe なしでコスト削減
marts:
+materialized: table
+transient: false # mart は通常テーブルで保護
クラスタリングキーでクエリ性能を最適化する
大規模なテーブルに対して、よく使われるフィルタ条件のカラムをクラスタリングキーに設定すると、Snowflake のマイクロパーティションが最適化されクエリ性能が向上します。
{{
config(
materialized='table',
cluster_by=['ordered_at']
)
}}
select
...
クラスタリングキーは数億行以上の大規模テーブルで効果を発揮します。小規模テーブルに設定してもコストだけかかるため、テーブルサイズに応じて判断してください。
Query Tag で dbt 発行クエリを識別する
Snowflake の QUERY_HISTORY ビューで dbt が発行したクエリを識別しやすくするために、Query Tag を設定できます。
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/