概要
社内のdbt基盤で新規テーブルを作成する機会があり、dbt初挑戦しました。
そこで学んだポイントを4つ紹介します。
1. dbtのレイヤー構成を理解する
dbtのレイヤー構成
dbtではデータを段階的に加工していく「レイヤー構成」が推奨されています。
代表的な構成は以下の通りです。
| レイヤー | 役割 |
|---|---|
| source | 生データの参照元を定義 |
| staging | sourceを軽く整形(カラム名変更、型変換など) |
| marts | 最終的な分析用テーブル |
詳細:How we structure our dbt projects | dbt Best Practices
ZOZOで実際に採用しているレイヤーについては以下をご参照ください。
sourceから順番に定義していく
各レイヤーを作成する際は、最上流となるテーブルを「source」として定義する必要があります。
# models/sources/sample_xxx.yml
sources:
- name: my_source
database: my-project
schema: my_dataset
tables:
- name: my_table
私はついこのレイヤー構造を忘れてダミーデータ作成に失敗しました。
「大元の参照先がないと下流のレイヤーは作れない」という当たり前のことですが...
sourceを定義すると、SQL内で{{ source('my_source', 'my_table') }}のように参照でき、dbt docsでリネージ(データの依存関係)が自動で可視化されます。
2. CTEの書き方にはdbt流のお作法がある
CTEとは
CTE(Common Table Expression)は、WITH句を使って一時的な結果セットに名前を付ける機能です。
サブクエリを変数のように扱えるため、複雑なクエリを読みやすく整理できます。
-- CTEを使わない場合(サブクエリのネスト)
select *
from (
select *
from (
select * from my_table where is_deleted = false
) as filtered
where event_date = '2025-01-01'
) as dated
-- CTEを使う場合
with filtered as (
select * from my_table where is_deleted = false
),
dated as (
select * from filtered where event_date = '2025-01-01'
)
select * from dated
CTEを使うことで、処理の流れが上から下へ自然に読めるようになります。
詳細:Getting started with CTEs | dbt Blog
dbt流のCTE構成
dbtでは「dbtonic」と呼ばれる書き方が推奨されています。
CTEの構成は以下の3つに分けるのがベストプラクティスです。
| CTE種別 | 役割 |
|---|---|
| import CTE | sourceやrefで外部テーブルを取得 |
| logical CTE | フィルタリングや変換などのビジネスロジック |
| final CTE | 最終的な出力を整形 |
-- import CTE
with source as (
select * from {{ source('my_source', 'my_table') }}
),
-- logical CTE
filtered as (
select * from source where is_deleted = false
),
-- final CTE
final as (
select
user_id,
event_timestamp,
content_id
from filtered
)
select * from final
最終SELECTはカラムを列挙せずにselect * にするところもポイントです。
この構成に従うことで、データ取得、加工、出力の流れが一目で分かります。
詳細:How we style our dbt projects | dbt Best Practices
3. dbt testでデータ品質を担保する
dbtではYAMLでテストを宣言的に定義できます。
テストはdbt buildやdbt test実行時に自動でチェックされます。
# models/marts/sample_test_xxx.yml
models:
- name: sample_test_xxx
columns:
- name: key
tests:
- unique
- not_null
- name: id
tests:
- not_null
- relationships:
to: ref('dim_users_id')
field: id
- name: name
tests:
- accepted_values:
values: ['piyo', 'hoge']
代表的なテストパターンは以下:
| テスト | 用途 |
|---|---|
| unique | 値が一意であることを確認 |
| not_null | NULLがないことを確認 |
| accepted_values | 許可された値のみであることを確認 |
| relationships | 他テーブルとの参照整合性を確認 |
4. dbt testのWARN/ERROR調査方法を把握しておく
調査手順の例:
- コンパイル済みSQLを取得
- SQLを直接実行
- 該当レコードの詳細を確認
コンパイル済みSQLは、Elementaryダッシュボード等の監視ツールから取得できます。
ローカル環境であればdbt compile実行後にtarget/compiled/配下に生成されます。
私のケースでは、Elementaryダッシュボードから該当SQLを取得し、マスタテーブルの更新タイミング差(データ遅延)が原因でWARNが発生したことを特定しました。
詳細:
dbt独自のお作法に関しては学習コストがありますが、かなり勉強になりました。
以上です。
どなたかのお役に立てれば幸いです。