【初心者向け】【入門】dbtとは？ディレクトリ構成、操作方法含めて徹底解説

## 概要
ETLのデータ変換（ETLのTransfer）を効率的に行いたい際にdbtを使うケースが増えています
今回はdbtをはじめて触る人向けに
- dbtの概要
- ディレクトリ構成
- 操作方法

などについて解説していきます

## 前提
- 今回は検証しやすいようにPostgresのコンテナを使って解説します
- パッケージ管理はuvを使用

## dbtとは？
データ変換（ETLのTransfer）を SQLベースでシンプルに実装できるツールです
従来は、Pythonや専用のツールを使って記述していましたが、dbtを使えばSQLの知識があればデータの
- 変換
- 管理
- テスト

などを効率的に行うことができます
データの抽出(ETLのExtract)やロード(ETLのLoad)は出来ませんが
- Google CloudのBigQuery
- AWSのRedshift

などのDWH(データウェアハウス)に読み込まれた生データを分析しやすい形へ変換することに優れています

## コンテナ環境構築
Postgresを使用するためのコンテナ環境を構築します
以下のディレクトリ構成で進めていきます
必要なファイルおよびフォルダは後ほど説明していきます

```
tree
.
├── .env
├── .gitignore
├── Makefile
├── README.md
├── application
│   ├── .python-version
│   ├── README.md
│   ├── pyproject.toml
│   └── uv.lock
├── containers
│   └── postgres
│       ├── Dockerfile
│       └── init.sql
└── docker-compose.yml
```

今回はPostgresを使用するのでdbt-coreとdbt-postgresをインストールします

```
uv add dbt-core
uv add dbt-postgres
```

- Dockerfile
- docker-compose.yml
- .env

にPostgresの設定を記載していきます

```Dockerfile
FROM postgres:17.3
```

```docker-compose.yml
services:
  db:
    container_name: db
    build:
      context: .
      dockerfile: containers/postgres/Dockerfile
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./containers/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      - POSTGRES_NAME
      - POSTGRES_USER
      - POSTGRES_PASSWORD
    ports:
      - "5432:5432" # デバッグ用
volumes:
  db_data:
```

環境変数を記載します
今回は
- DB名
- ユーザ名
- パスワード

をすべてpostgresにします
```.env
POSTGRES_NAME=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
```

init.sqlをマウントしてテーブルとデータを作成するSQLを記載します

```init.sql
-- スキーマ作成
-- Postgresではschemaがデフォルトでpublicになるため、dbt用のschemaを作成する
CREATE SCHEMA dbt_dev;

-- Users テーブルの作成
CREATE TABLE "postgres"."dbt_dev"."users" (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    username VARCHAR(255) UNIQUE NOT NULL,
    first_name VARCHAR(255),
    last_name VARCHAR(255),
    password VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    role VARCHAR(50),
    phone_number VARCHAR(20)
);

-- Todos テーブルの作成
CREATE TABLE "postgres"."dbt_dev"."todos" (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    priority INT,
    complete BOOLEAN DEFAULT FALSE
);

-- Users テーブルにテストデータを挿入
INSERT INTO "postgres"."dbt_dev"."users" (email, username, first_name, last_name, password, is_active, role, phone_number)
VALUES
    ('user1@example.com', 'user1', 'John', 'Doe', 'hashedpassword1', TRUE, 'admin', '123-456-7890'),
    ('user2@example.com', 'user2', 'Jane', 'Smith', 'hashedpassword2', TRUE, 'user', '234-567-8901'),
    ('user3@example.com', 'user3', 'Alice', 'Johnson', 'hashedpassword3', FALSE, 'user', '345-678-9012');

-- Todos テーブルにテストデータを挿入
INSERT INTO "postgres"."dbt_dev"."todos" (title, description, priority, complete)
VALUES
    ('Buy groceries', 'Buy milk, eggs, and bread', 2, FALSE),
    ('Finish project', 'Complete the database migration', 1, FALSE),
    ('Call mom', 'Check in with mom and chat', 3, TRUE);

```

以上のファイル群を作成した後はコンテナのbuild、upを実行します
その後、コンテナ内に入り、Postgresの起動、DB、テーブル、データの作成を確認できれば成功です

```
docker-compose up -d --build
```

```
docker exec -it db bash
root@a1c3e2cf55fb:/# psql -U postgres
psql (17.3 (Debian 17.3-1.pgdg120+1))
Type "help" for help.

postgres=# \c postgres
You are now connected to database "postgres" as user "postgres".
postgres=# \dt dbt_dev.*
         List of relations
 Schema  | Name  | Type  |  Owner
---------+-------+-------+----------
 dbt_dev | todos | table | postgres
 dbt_dev | users | table | postgres
(2 rows)

postgres=# select * from "dbt_dev"."todos";
 id |     title      |           description           | priority | complete
----+----------------+---------------------------------+----------+----------
  1 | Buy groceries  | Buy milk, eggs, and bread       |        2 | f
  2 | Finish project | Complete the database migration |        1 | f
  3 | Call mom       | Check in with mom and chat      |        3 | t
(3 rows)

postgres=# select * from "dbt_dev"."users";
 id |       email       | username | first_name | last_name |    password     | is_active | role  | phone_number
----+-------------------+----------+------------+-----------+-----------------+-----------+-------+--------------
  1 | user1@example.com | user1    | John       | Doe       | hashedpassword1 | t         | admin | 123-456-7890
  2 | user2@example.com | user2    | Jane       | Smith     | hashedpassword2 | t         | user  | 234-567-8901
  3 | user3@example.com | user3    | Alice      | Johnson   | hashedpassword3 | f         | user  | 345-678-9012
(3 rows)
```

## dbtのディレクトリ構成の説明
dbtのプロジェクトにおいてデータ変換やテスト、ドキュメント管理のための一般的なフォルダ構成が存在します
以下のフォルダおよびファイルを作成しましょう

| フォルダ名        | 説明 | 役割・ポイント |
|------------------|----------------------------------|----------------------------------|
| models/       |SQLモデルを定義するフォルダ | `dbt run` で実行する SQL を配置 |
| seeds/`       | CSV ファイルをデータとして取り込む** | `dbt seed` で DWH にロード |
| tests/       | データ品質をテストする SQL を格納 | `dbt test` で NULL チェックなど |
| snapshots/    | データの履歴を管理する | `dbt snapshot` で SCD（履歴管理）を実装 |
| macros/       | 再利用可能な SQL テンプレート | Jinja で SQL を動的に生成 |
| analysis/     | 分析用の SQL クエリを保存 | `dbt run` には含まれない |
| target/       | dbt 実行時の出力結果を保存 | `dbt run` の成果物やログを格納 |
| logs/         | 実行時のログ情報を記録 | デバッグ・エラー調査に使用 |
| dbt_project.yml | プロジェクト設定ファイル | モデルのスキーマ・パス設定など |
| packages.yml  | dbtの外部ライプラリ情報 | dbtの外部ライプラリを管理 (`~/.dbt/` に配置) |
| package-lock.yml  | dbtの外部ライプラリ情報 | dbtの外部ライプラリの依存関係を管理 (`~/.dbt/` に配置) |
| profiles.yml  | データベース接続情報 | DWH の認証情報を設定 (`~/.dbt/` に配置) |

```
tree
├── .env
├── .gitignore
├── Makefile
├── README.md
├── application
│   ├── .python-version
│   ├── .venv
│   ├── README.md
│   ├── analysis
│   ├── dbt_project.yml
│   ├── logs
│   ├── macros
│   ├── main.py
│   ├── models
│   ├── profiles.yml
│   ├── pyproject.toml
│   ├── seeds
│   ├── snapshots
│   ├── target
│   ├── tests
│   └── uv.lock
├── containers
│   └── postgres
│       ├── Dockerfile
│       └── init.sql
└── docker-compose.yml
```

上記のディレクトリ構成内で初期設定に必要なファイルは以下の通りです
- dbt_project.yml
- profiles.yml

順番に解説していきます

### dbt_project.yml
dbtのプロジェクトに必須のファイルでプロジェクトの設定を記載します


```dbt_project.yml
# https://docs.getdbt.com/reference/dbt_project.yml
# プロジェクト名
name: 'dbt_practice'
config-version: 2
version: '1.0.0'

# プロファイル、DWの接続設定名
profile: 'dbt_practice'

# 各フォルダのパス
model-paths: ["models"]
analysis-paths: ["analysis"]
test-paths: ["tests"]
seed-paths: ["seeds"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]

# dbt コマンドの出力先パス
target-path: "target"
# dbt clean コマンドを実行したときに削除対象のディレクトリ
clean-targets: [target, dbt_packages]

# どのmodelをどのスキーマと紐付けるかの設定
models:
  dbt_practice:
    schema: dbt_dev

```

### profiles.yml
DWHとの接続情報を定義するファイルです
今回はPostgresを使用しているので
- ホスト
- ユーザ名
- パスワード
- ポート
- DB名
- スキーマ

などの設定を記載します

```profiles.yml
dbt_practice:
  target: dev
  outputs:
    dev:
      type: postgres
      host: localhost
      user: postgres
      password: postgres
      port: 5432
      dbname: postgres
      schema: dbt_dev
      threads: 1
      keepalives_idle: 0
      connect_timeout: 10
```

### packages.yml
- dbt_utils
- elementary

などdbt-coreのデフォルトの機能だけで対応できない実装などをする際に外部ライブラリを利用することが多いです
packages.ymlにdbt用のライブラリ情報を記載していきます

```packages.yml
# This is your packages.yml file. This is where you declare packages that your project depends on.
# For full documentation: https://docs.getdbt.com/docs/building-a-dbt-project/package-management

packages:

# The dbt_utils package is a package we recommend everyone installs. This package solves common questions of `How do I do this in SQL?`. 
# To learn more, check out the docs: https://hub.getdbt.com/dbt-labs/dbt_utils/latest/
  - package: dbt-labs/dbt_utils
    version: 1.3.0
## Docs: https://docs.elementary-data.com
  - package: elementary-data/elementary
    version: 0.16.1
```

dbtで使用するライブラリはdbt depsコマンドを使ってインストールします

```
dbt deps
02:14:57  Running with dbt=1.9.2
02:14:58  Updating lock file in file path: /Users/shun/dbt-practice/application/package-lock.yml
02:14:58  Installing dbt-labs/dbt_utils
02:14:58  Installed from version 1.3.0
02:14:58  Up to date!
02:14:58  Installing elementary-data/elementary
02:14:59  Installed from version 0.16.1
02:14:59  Updated version available: 0.16.4
02:14:59
02:14:59  Updates available for packages: ['elementary-data/elementary']
Update your versions in packages.yml, then run dbt deps
```

### package-lock.yml
packages.ymlのlockファイルでdbt depsコマンドを実行すれば自動生成されます
今回インストールした
- dbt_utils
- elementary

の依存関係を管理します

```package-lock.yml
packages:
  - package: dbt-labs/dbt_utils
    version: 1.3.0
  - package: elementary-data/elementary
    version: 0.16.1
sha1_hash: 5e638e51b8a19f6d626e17b8fbaeb0b4c026abd1

```

dbt_utilsやelementaryの使い方などは今後別記事で解説したいと思います

## modelの作成
dbtにおけるmodelはデータを変換・加工するためのSQLクエリを指します
ディレクトリ構成で記載したmodels/フォルダ内に.sqlファイルを作成し、そこにSELECTクエリを書くことで、データの成形・整理を行います
今回はusersテーブルのfirst_nameとlast_nameを結合し、full_nameカラムを作成します

```application/models/user_name.sql
select
	"id",
	concat("first_name", " ", "last_name") as full_name
from
	"postgres"."dbt_dev"."users"

```

dbt runコマンドを実行するとuser_name という新しいdbtモデル(view)が定義され、クエリの結果が DWH(今回だとPostgres)内のviewに保存されます
以下のようにviewが作成され、user_nameのviewにlast_nameのカラム、userテーブル内のfirst_nameとlast_nameが結合されたデータが保存されたら成功です

```
dbt run
01:45:28 Running with dbt=1.9.2
01:45:28 Registered adapter: postgres=1.9.0
01:45:28 [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:

- models.application
  01:45:28 Found 1 model, 433 macros
  01:45:28
  01:45:28 Concurrency: 1 threads (target='dev')
  01:45:28
  01:45:29 1 of 1 START sql view model dbt_dev.user_name .................................. [RUN]
  01:45:29 1 of 1 OK created sql view model dbt_dev.user_name ............................. [CREATE VIEW in 0.10s]
  01:45:29
  01:45:29 Finished running 1 view model in 0 hours 0 minutes and 0.28 seconds (0.28s).
  01:45:29
  01:45:29 Completed successfully
  01:45:29
  01:45:29 Done. PASS=1 WARN=0 ERROR=0 SKIP=0 TOTAL=1

```

```
postgres=# select * from dbt_dev.user_name;
id | full_name
----+---------------
1 | John Doe
2 | Jane Smith
3 | Alice Johnson
(3 rows)

```

view の一覧は以下のコマンドを実行すれば確認できます

```
postgres=# \dv dbt_dev_dbt_dev.*
               List of relations
     Schema      |   Name    | Type |  Owner   
-----------------+-----------+------+----------
 dbt_dev_dbt_dev | user_name | view | postgres
(1 row)
```

## テストの実行
dbtでは作成したviewの単体テストを作成できます
単体テストを作成することで該当カラムにNULLが入らないことや外部キーが正しく紐付いているか、などを担保することができます
今回はuser_nameのviewのfull_nameカラムがnullにならないテストを作成します

```application/tests/user_name_is_not_null.sql
-- full_nameがNULLの時、idを返すテスト
select
    id
from
    {{ ref('user_name') }}
where
    full_name is null

```

テストを実行するときはdbt testコマンドを実行します
以下のようにテストが実行され、通ることが確認できれば成功です

```
dbt test
03:03:51  Running with dbt=1.9.2
03:03:51  Registered adapter: postgres=1.9.0
03:03:52  Found 30 models, 2 seeds, 2 operations, 1 test, 1235 macros
03:03:52
03:03:52  Concurrency: 1 threads (target='dev')
03:03:52
03:03:52
IMPORTANT - Starting from dbt 1.8, users must explicitly allow packages to override materializations.
Elementary requires this ability to support collection of samples and failed row count for dbt tests.
Please add the following flag to dbt_project.yml to allow it:

flags:
  require_explicit_package_overrides_for_builtin_materializations: false

Notes -
* This is a temporary measure that will result in a deprecation warning, please ignore it for now. Elementary is working with the dbt-core team on a more permanent solution.
* This message can be muted by setting the 'mute_ensure_materialization_override' var to true.

03:03:52  1 of 1 START hook: elementary.on-run-start.0 ................................... [RUN]
03:03:52  1 of 1 OK hook: elementary.on-run-start.0 ...................................... [OK in 0.10s]
03:03:52
03:03:52  1 of 1 START test user_name_is_not_null ........................................ [RUN]
03:03:52  1 of 1 PASS user_name_is_not_null .............................................. [PASS in 0.07s]
03:03:52
03:03:54  1 of 1 START hook: elementary.on-run-end.0 ..................................... [RUN]
03:03:54  1 of 1 OK hook: elementary.on-run-end.0 ........................................ [OK in 1.65s]
03:03:54
03:03:54  Finished running 2 project hooks, 1 test in 0 hours 0 minutes and 2.08 seconds (2.08s).
03:03:54
03:03:54  Completed successfully
03:03:54
03:03:54  Done. PASS=3 WARN=0 ERROR=0 SKIP=0 TOTAL=3
```


## dbt ドキュメントの生成と閲覧
- dbt docs generate
- dbt docs serve

コマンドを使用することでdbtドキュメントの生成およびローカルホスト上でdbtドキュメントを閲覧することができるようになります

```
dbt docs generate
02:23:11  Running with dbt=1.9.2
02:23:12  Registered adapter: postgres=1.9.0
02:23:12  [WARNING]: Configuration paths exist in your dbt_project.yml file which do not apply to any resources.
There are 1 unused configuration paths:
- models.application
02:23:12  Found 30 models, 2 operations, 1235 macros
02:23:12
02:23:12  Concurrency: 1 threads (target='dev')
02:23:12
02:23:13
IMPORTANT - Starting from dbt 1.8, users must explicitly allow packages to override materializations.
Elementary requires this ability to support collection of samples and failed row count for dbt tests.
Please add the following flag to dbt_project.yml to allow it:

flags:
  require_explicit_package_overrides_for_builtin_materializations: false

Notes -
* This is a temporary measure that will result in a deprecation warning, please ignore it for now. Elementary is working with the dbt-core team on a more permanent solution.
* This message can be muted by setting the 'mute_ensure_materialization_override' var to true.

02:23:14  Building catalog
02:23:14  Catalog written to /Users/shun/dbt-practice/application/target/catalog.json

dbt docs serve
02:23:32  Running with dbt=1.9.2
Serving docs at 8080
To access from your browser, navigate to: http://localhost:8080



Press Ctrl+C to exit.
127.0.0.1 - - [16/Feb/2025 11:23:33] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [16/Feb/2025 11:23:33] "GET /manifest.json?cb=1739672613865 HTTP/1.1" 200 -
127.0.0.1 - - [16/Feb/2025 11:23:33] "GET /catalog.json?cb=1739672613865 HTTP/1.1" 200 -
127.0.0.1 - - [16/Feb/2025 11:23:35] code 404, message File not found
127.0.0.1 - - [16/Feb/2025 11:23:35] "GET /$%7Brequire('./assets/favicons/favicon.ico')%7D HTTP/1.1" 404 -
```

`http://localhost:8080`へアクセスすると閲覧できます

![スクリーンショット 2025-02-16 15.07.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/625980/287b879b-3252-4093-aa81-57b2c93451f0.png)

## DBとの接続テストおよびデバッグ用の情報の表示したい時
dbt debugコマンドを使用することで
- DBとの接続テスト
- デバッグ用の情報の表示

を行うことができます

```
dbt debug
03:12:50  Running with dbt=1.9.2
03:12:50  dbt version: 1.9.2
03:12:50  python version: 3.11.11
03:12:50  python path: /Users/shun/dbt-practice/application/.venv/bin/python3
03:12:50  os info: macOS-14.6.1-x86_64-i386-64bit
03:12:50  Using profiles dir at /Users/shun/dbt-practice/application
03:12:50  Using profiles.yml file at /Users/shun/dbt-practice/application/profiles.yml
03:12:50  Using dbt_project.yml file at /Users/shun/dbt-practice/application/dbt_project.yml
03:12:50  adapter type: postgres
03:12:50  adapter version: 1.9.0
03:12:51  Configuration:
03:12:51    profiles.yml file [OK found and valid]
03:12:51    dbt_project.yml file [OK found and valid]
03:12:51  Required dependencies:
03:12:51   - git [OK found]

03:12:51  Connection:
03:12:51    host: localhost
03:12:51    port: 5432
03:12:51    user: postgres
03:12:51    database: postgres
03:12:51    schema: dbt_dev
03:12:51    connect_timeout: 10
03:12:51    role: None
03:12:51    search_path: None
03:12:51    keepalives_idle: 0
03:12:51    sslmode: None
03:12:51    sslcert: None
03:12:51    sslkey: None
03:12:51    sslrootcert: None
03:12:51    application_name: dbt
03:12:51    retries: 1
03:12:51  Registered adapter: postgres=1.9.0
03:12:51    Connection test: [OK connection ok]

03:12:51  All checks passed!
```

## まとめ
dbtの基礎的な知識についてまとめてみましたがいかがでしたでしょうか？
他に汎用的で便利な使用用途があれば随時更新していきたいと思います

## 参考
https://docs.getdbt.com/best-practices/how-we-structure/1-guide-overview

https://zenn.dev/dbt_tokyo/books/537de43829f3a0/viewer/what_dbt

https://zenn.dev/foursue/books/31456a86de5bb4/viewer/04bca4

https://docs.getdbt.com/reference/commands/run

https://docs.getdbt.com/docs/core/pip-install

https://docs.getdbt.com/docs/build/unit-tests