dbt を使おうとすると、profile や target、config、property など、様々な概念が出てくる。
それらをあまり理解できていなくても、何となく動かすことはできるかもしれない。
しかし、これらの概念を理解していないと、意図した通りに動かしたり他者が記述した設定内容を理解したりするのは難しい。
この記事では、最初は理解が難しいこれらの概念について、「model の出力先の設定」を題材にして説明していく。
dbt runした際にどこにテーブルやビューが出力されるのか、設定を読み解いて理解できるようになることを目指す。
この記事の内容は dbt のバージョン1.8.5で動作確認している。
データウェアハウスは BigQuery を使用する。
概要
どのデータウェアハウスのどのテーブルにあるデータを使うのか、データの出力先はどこになるのか。
そういった設定には「profile による基本設定」と「config による個別設定」の 2 つがあり、個別設定によって基本設定を上書きすることができる。
この仕組みによって、特定の model についてだけ出力先を変える、といったことが可能になる。
まずは基本設定から見ていく。
基本設定
dbt では profile という概念によって、データウェアハウスとの接続に関する情報を扱う。
そしてその profile について記述するためのファイルが、profiles.yml。このファイルに、どのデータウェアハウスを使うのか、どのデータベースを使うのか、といったことを記述していく。
以下は、データウェアハウスとして BigQuery を使い、dbt-project-a-432612というプロジェクトのdestデータセットに model を出力する場合のprofiles.yml。
my_profile_1: # profile の名前 target: dev # デフォルトで使う target の名前 outputs: dev: # target の名前 type: bigquery # 接続先のデータウェアハウスの種類 method: oauth # 認証に使う方法 project: dbt-project-a-432612 # 使用するプロジェクト dataset: dest # 使用するデータセット
ひとつの profile は、ひとつ以上の target を持つ。データウェアハウスについての具体的な情報は target に書く。target はprofiles.ymlの<profile-name>.outputsに書いていく。
上記の例では、devという target を定義している。
詳細は後述するが、ひとつの profile のなかで複数の target を定義できるため、デフォルトで使う target を指定しておくことができる。profiles.ymlの<profile-name>.targetで指定する。
上記の例ではtarget: devがそれにあたる。
上記の例では target で 4 つの項目を設定している。
このうちmethodは認証に関するものであり、この記事の内容との関係は薄いので割愛する。
詳細は、公式ドキュメントの以下のページで確認できる。
https://docs.getdbt.com/docs/core/connect-data-platform/bigquery-setup
重要なのはprojectとdatasetである。これで、接続先を指定している。
項目名はデータウェアハウスによって異なるので注意する。例えば Snowflake の場合はdatabaseやschemaを使って設定を行う。
https://docs.getdbt.com/reference/dbt-jinja-functions/target
projectは、データの出力先として使われるだけでなく、source の参照先としても使われる。
例えば以下のように source を定義してfrom {{ source('src', 'user') }}とする場合、dbt-project-a-432612プロジェクトのsrcデータセットのuserテーブルが使われる。
version: 2 sources: - name: src # dataset を省略した場合は name が dataset として使われる tables: - name: user columns: - name: id - name: name
dbt-project-a-432612.src.userを用意し、それを参照しているfooという model を書いて$ dbt run -s fooを実行してみると……。
エラーになる。
$ dbt run -s foo 15:48:52 Running with dbt=1.8.5 15:48:52 Encountered an error: Runtime Error No dbt_project.yml found at expected path <カレントディレクトリのパス>/dbt_project.yml Verify that each entry within packages.yml (and their transitive dependencies) contains a file named dbt_project.yml
dbt runを実行するためには、dbt_project.ymlを用意する必要がある。そして、dbt_project.ymlで、どの profile を使うか指定しなければならない。
name: 'my_dbt_project' config-version: 2 version: '1.0.0' profile: 'my_profile_1' # profiles.yml で定義した my_profile_1 を指定
nameとversionに、この dbt プロジェクトの名前とバージョンを指定する。config-versionは2にする決まり。
そしてprofileキーに、使用する profile を指定する。
この状態で$ dbt run -s fooすると今度は上手くいき、dbt-project-a-432612.dest.fooというビューが作られる。
target の使い分け
ひとつの profile に対して、複数の target を定義できる。
以下の例では、先ほど定義したdevに加えて、prodという target を定義した。そしてprodではdbt-project-b-432615プロジェクトを使うと定義している。
my_profile_1: # profile の名前 target: dev # デフォルトで使う target の名前 outputs: dev: # target の名前 type: bigquery method: oauth project: dbt-project-a-432612 dataset: dest prod: # target の名前 type: bigquery method: oauth project: dbt-project-b-432615 dataset: dest
dbt runする際に--targetフラグを使うことで、使用する target を指定できる。
例えば$ dbt run -s foo --target prodを実行すると、dbt-project-b-432615.dest.fooビューが作られる。その際はもちろん、dbt-project-b-432615.src.userが source として使われる。
--targetフラグを使わなかった場合は、<profile-name>.targetで指定した target (上記の例だとdev)が使われる。
--targetフラグを使わず<profile-name>.targetも定義されていなかった場合はエラーになる。
$ dbt run -s foo 16:57:30 Running with dbt=1.8.5 16:57:30 target not specified in profile 'my_profile_1', using 'default' 16:57:30 Encountered an error: Runtime Error The profile 'my_profile_1' does not have a target named 'default'. The valid target names for this profile are: - dev - prod
config
model 毎に、その出力先を設定することができる。
その仕組みを理解するためにはまず、config という概念を理解する必要がある。
model に対しては property を設定できる(model だけでなく seed や test などにも property を設定できるが、この記事では割愛する)。
そして property の中には、 config と呼ばれる特殊な property がある。
config は以下の 3 つの方法で設定できる。
config()関数- 個別の model について記述した
.ymlファイル dbt_project.yml
上記の順番で優先度が高く、ひとつの config に対して複数の方法で設定されていた場合、優先度が高い方法で設定した内容が採用される。
詳細はこれから具体例を用いて説明していくので、config には複数の設定方法がある、優先順位が決まっている、ということをまずは覚えておけばよい。
model に設定できる config のうち、model の出力先に関係するのがdatabaseとschema。
BigQuery の場合はprojectとdatasetという config も設定できる。それぞれ、databaseとschemaと互換性がある。
BigQuery の用語(プロジェクトとデータセット)と合わせたほうが分かりやすいので、この記事ではprojectとdatasetを使うことにする。
dbt_project.yml で config を設定する
まずはdbt_project.ymlでproject config を使ってみる。
name: 'my_dbt_project' config-version: 2 version: '1.0.0' profile: 'my_profile_1' models: my_dbt_project: # name で指定した project の名前を書く marts: # marts ディレクトリ以下にある model は…… +project: dbt-project-x # dbt-project-x に出力される
model に対する config はmodels:の下に書いていく。まずは project の名前を書き、その下に具体的な設定を書いていく。
今回はmartsディレクトリ以下の model に対して config を設定したいので、まずはmartsと書く。そして、+<config名>: 設定内容を書く。上記の例ではproject config にdbt-project-xを設定している。
こうすると、martsディレクトリ以下にある model はdbt-project-xに出力されるようになる。
例えばmodels/の中身が以下のときに$ dbt run --target devすると、dbt-project-a-432612.dest.fooとdbt-project-x.dest.barが作られる。
models ├── foo.sql ├── marts │ └── bar.sql └── sources.yml
target としてdevを指定したので、profiles.ymlに記した内容に基づき、接続先はdbt-project-a-432612.destになる。
しかしmarts以下のディレクトリについてはdbt-project-xに出力するようにdbt_project.ymlに記したので、その設定が優先される。datasetについてはdbt_project.ymlで特に設定していないので、devで設定したdestがそのまま使われる。
そのため、marts/bar.sqlの実行結果がdbt-project-x.dest.barに書き込まれるのである。
次はdatasetも使ってみる。
dbt_project.ymlに+dataset: my_suffix_1を書き加える。
name: 'my_dbt_project' config-version: 2 version: '1.0.0' profile: 'my_profile_1' models: my_dbt_project: # name で指定した project の名前を書く marts: # marts ディレクトリ以下にある model は…… +project: dbt-project-x # dbt-project-x に出力される +dataset: my_suffix_1 # target.dataset に _my_suffix_1 という接尾辞をつけたデータセットに出力される
この状態で$ dbt run --target devすると、dbt-project-a-432612.dest.fooとdbt-project-x.dest_my_suffix_1.barが出力される。
my_suffix_1というデータセットに出力されるのではなく、target.dataset(今回の場合dest)とmy_suffix_1を_でつないだデータセットに出力されるので、注意が必要。
model のprojectやdataset config で設定しているのはあくまでも、「model の出力先の設定」である。「source としてどのデータベースを参照するか」には影響を与えない。
そのためのfooもbarも、from {{ source('src', 'user') }}している場合はdbt-project-a-432612.src.userを参照するので注意する。
config を入れ子にする
以下のように config をネストさせていくことも可能。
name: 'my_dbt_project' config-version: 2 version: '1.0.0' profile: 'my_profile_1' models: my_dbt_project: # name で指定した project の名前を書く marts: # marts ディレクトリ以下にある model は…… +project: dbt-project-x # dbt-project-x に出力される +dataset: my_suffix_1 # target.dataset に _my_suffix_1 という接尾辞をつけたデータセットに出力される special: # marts/special ディレクトリ以下にある model は…… +dataset: my_suffix_2 # target.dataset に _my_suffix_2 という接尾辞をつけたデータセットに出力される
このように書くと、marts/specialディレクトリ以下にある model はdest_my_suffix_2データセットに出力されるようになる。
marts/special/baz.sqlを用意して確認してみる。
models ├── foo.sql ├── marts │ ├── bar.sql │ └── special │ └── baz.sql └── sources.yml
この状態で$ dbt run --target devすると、以下のビューが作られる。
dbt-project-a-432612.dest.foodbt-project-x.dest_my_suffix_1.bardbt-project-x.dest_my_suffix_2.baz
specialで設定したのはdatasetのみなので、projectはmartsで設定したdbt-project-xがmarts/special/baz.sqlにも適用される。
個別の model について記述した .yml ファイルで config を設定する
model と同じディレクトリに.ymlファイルを用意し、そこに個別の model に対する property を記述することができる。
config も property の一部なので、projectやdatasetもそのファイルに書くことができる。
例として、models/marts/special/qux.sqlという model を用意し、その model について記述したmodels/marts/special/qux.ymlを用意する。
models/marts/special/qux.ymlには以下の内容を書く。
version: 2 models: - name: qux config: project: dbt-project-y
quxのprojectをdbt-project-yにしている。
datasetは特に上書きしていないので、dbt_project.ymlの内容がそのまま使われる。qux.sqlはmarts/special以下にあるので、今回の例だとmy_suffix_2になる。
その結果、models/marts/special/qux.sqlの内容はdbt-project-y.dest_my_suffix_2.quxに出力される。
このように、個別の model について記述した.ymlファイルで設定した内容は、dbt_project.ymlの内容よりも優先される。
config() 関数で config を設定する
最後に、最も優先順位が高いconfig()関数を使った方法について述べる。
先ほど用意したmodels/marts/special/qux.sqlの内容を以下のようにする。
{{ config(
project='dbt-project-z'
) }}
select name from {{ source('src', 'user') }}
そうすると、出力先はdbt-project-z.dest_my_suffix_2.quxになる。
このように、.ymlで定義した内容(今回の例だとproject: dbt-project-y)よりも、config()関数の内容(今回の例だとproject='dbt-project-z')が優先される。
generate_database_name と generate_schema_name
ここまで、model の出力先がどのように決まるのか見てきたが、その挙動は実は macro によって制御されている。
dbt には予めgenerate_database_nameという macro とgenerate_schema_nameという marco が用意されている。
そしてこれらの macro はdbt runなどを実行したときに暗黙的に呼び出され、そこに書かれているロジックによって macro の出力先が決まる。
そしてどちらの macro も、自分で定義して既存の振る舞いを上書きすることができる。
generate_database_name
generate_database_nameは、出力先の database を決めるための macro 。projectはdatabaseと互換性があるため、databaseはそのままprojectに読み替えてしまって問題ない。
デフォルトの実装は以下のようになっている。
{% macro generate_database_name(custom_database_name=none, node=none) -%}
{%- set default_database = target.database -%}
{%- if custom_database_name is none -%}
{{ default_database }}
{%- else -%}
{{ custom_database_name | trim }}
{%- endif -%}
{%- endmacro %}
引数のcustom_database_nameは、 model に設定されたdatabase(もしくはproject) config 。
これが設定されない場合はdefault_database、つまりtarget.database(BigQuery の場合はtarget.project)が使われる。
custom_database_nameが設定されている場合は、それがそのまま(trimだけして)使われる。
このように、この記事でここまで説明してきた挙動は、この macro によって定義されているのである。
generate_schema_name
generate_schema_nameも同様に、出力先の schema を決定するロジックを定義している。datasetはschemaと互換性があるため、schemaはそのままdatasetに読み替えてしまって問題ない。
デフォルトの実装は以下。
{% macro generate_schema_name(custom_schema_name, node) -%}
{%- set default_schema = target.schema -%}
{%- if custom_schema_name is none -%}
{{ default_schema }}
{%- else -%}
{{ default_schema }}_{{ custom_schema_name | trim }}
{%- endif -%}
{%- endmacro %}
独自定義によるオーバーライド
macros/generate_database_name.sqlあるいはmacros/generate_schema_name.sqlを定義することで、挙動を上書きすることができる。
例として、以下の内容でmacros/generate_database_name.sqlを作成して、出力先の database(もしくは project)を決めるロジックを自分で定義してみる。
{% macro generate_database_name(custom_database_name=none, node=none) -%}
{{ 'dbt-project-y' }}
{%- endmacro %}
このように定義してdbt runすると、target や config でprojectをどのように設定していても、その設定は使われず、全ての model がdbt-project-yに出力されるようになる。
source の接続先
projectやdatasetは source にも設定できる。
しかし source に対してはgenerate_database_nameやgenerate_schema_nameが実行されることはなく、projectやdatasetに設定した値がそのまま使われる。
そのため、以下のように設定した状態でfrom {{ source('src', 'user') }}とすると、dbt-project-b-432615.src.userが参照される。
version: 2 sources: - name: src # dataset を省略した場合は name が dataset として使われる project: dbt-project-b-432615 tables: - name: user columns: - name: id - name: name
target によって参照先を変えるということも可能。
以下のようにすると、devのときはdbt-project-a-432612を、prodのときはdbt-project-b-432615を参照するようになる。
version: 2 sources: - name: src project: | {%- if target.name == "dev" -%} dbt-project-a-432612 {%- elif target.name == "prod" -%} dbt-project-b-432615 {%- else -%} invalid_database {%- endif -%} tables: - name: user columns: - name: id - name: name
そして例えばprofiles.ymlを以下のようにしておくと、--target devでdbt runしたときはdbt-project-a-432612.src.userを参照してdbt-project-xに model を出力し、--target prodのときはdbt-project-b-432615.src.userを参照してdbt-project-yに model を出力するようになる。
config で model の出力先を設定している場合は、もちろんそれが採用される。
my_profile_1: # profile の名前 target: dev # デフォルトで使う target の名前 outputs: dev: # target の名前 type: bigquery method: oauth project: dbt-project-x dataset: dest prod: # target の名前 type: bigquery method: oauth project: dbt-project-y dataset: dest
参考資料
- About dbt Core data platform connections | dbt Developer Hub
- Connection profiles | dbt Developer Hub
- About target variables | dbt Developer Hub
- dbt_project.yml | dbt Developer Hub
- Configs, properties, what are they? | dbt Developer Hub
- Model configurations | dbt Developer Hub
- Custom databases | dbt Developer Hub
- Custom schemas | dbt Developer Hub
- [CT-260] [Bug]
generate_database_namedoes not apply to sources · Issue #4753 · dbt-labs/dbt-core