dbt-osmosisの挙動を調べてみた　〜①標準機能編〜

:::note warn
**2025/04/07 追記**

2025年1月にメジャーアップデートが入り、従来から挙動が変わっている箇所がいくつかあります。

詳細はこちらをご確認ください。

* [Migration Guide: Upgrading to dbt-osmosis 1.x.x | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/migrating)
* [dbt-osmosis/CHANGELOG.md at main · z3z1ma/dbt-osmosis](https://github.com/z3z1ma/dbt-osmosis/blob/main/CHANGELOG.md)
:::

最近 dbt-osmosis を触って挙動などを調べてみたのでその結果を記事にまとめてみます。

- ①標準機能編　←イマココ
- [②Refactor機能のコマンドオプション編](https://qiita.com/yamamoto-yuta/items/df2add3a8cdeae131ec2)

この記事では dbt-osmosis の導入方法や標準機能の挙動についてまとめています。

## dbt-osmosis とは？

公式リポジトリ:

https://github.com/z3z1ma/dbt-osmosis

dbt-osmosis は YAML ファイル（ソース YAML ファイル、スキーマ YAML ファイル）を自動生成してくれたり、上流テーブルのカラムの description などを下流へ伝搬してくれたりするツールです。

dbt-osmosis の有用性は次のスライドのp19~21が分かりやすいです。

<script async class="speakerdeck-embed" data-id="f8cf746644884a24a182ec8871c371f9?slide=19" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

## 導入

### STEP 1｜インストール

```
pip install dbt-osmosis
```

> 公式ドキュメント: [Installation | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/installation/) 

### STEP 2｜dbt_project.yml にスキーマ YAML ファイルの生成先を指定する

指定方法:

```yaml
models:
  <your_project_name>:
    +dbt-osmosis: <path>   # models/ からの相対パスとして扱われます
```

指定方法いろいろ:

```yaml
models:
  your_project_name:
    # プロジェクト全体に適用される設定
    +dbt-osmosis: "_{model}.yml" # {model} はモデル名を参照できるコンテキスト変数

    staging:
      # サブフォルダを指定することも可能（この場合、 models/schema/ ディレクトリ内にスキーマ YAML ファイルが生成される）
      +dbt-osmosis: "schema/{model}.yml"

    intermediate:
      # Materialization に基づいてファイルを分けることも可能
      +dbt-osmosis: "{node.config[materialized]}/{model}.yml" # {node} はノード情報を参照できるコンテキスト変数。ノード情報は manifest.json で確認できる

    marts:
      # 固定ファイルを指定することも可能
      +dbt-osmosis: "prod.yml"    

    utilities:
      # 親フォルダの名前を指定することも可能
      +dbt-osmosis: "{parent}.yml" # {parent} は親フォルダ名を参照できるコンテキスト変数
```

> 公式ドキュメント:
> - [Configuration | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-yaml/configuration)
> - [Context Variables | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-yaml/context)

## 標準機能

dbt-osmosis の標準機能を試してみた結果を下記に示します。

実行環境は次のとおりです:

```requirements.txt
dbt-bigquery==1.6
dbt-osmosis==0.12.9
```

### Document

#### 機能

> - YAML ファイルのカラムの順番を変更してデータベースのカラムの順番に合わせる
> - データベースに存在するカラムを YAML ファイルに追加する
> - データベースに存在しないカラムを YAML ファイルから削除する
> - カラムレベルのドキュメントを上流モデルから下流モデルに受け渡す (下流モデルにそのカラムのドキュメントがない場合)
> 
> ※ [公式ドキュメント](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/commands#document) を DeepL で翻訳したもの

```
dbt-osmosis yaml document [--project-dir] [--profiles-dir] [--target]
```

#### 実際に試してみた

**前準備**

:one: 各種ファイルを配置

次のディレクトリ構造で各種ファイルを配置します。

```:ディレクトリ構造
.
|-- dbt_project.yml
|-- models/
    |-- _source.yml
    |-- customers.sql
    |-- stg_customers.ql
    |-- stg_orders.sql
```

dbt_project.yml の `models:` の箇所を次のようにします。

```yml:dbt_project.yml（models:の部分のみ抜粋）
models:
  my_dbt_project:
    +materialized: table
    +dbt-osmosis: "_schema.yml"
```

_source.yml の中身を次のようにします。

```_source.yml
version: 2

sources:
  - name: YOUR_GC_DATASET
    database: YOUR_GC_PROJECT_ID
    tables:
      - name: raw_customers
      - name: raw_orders
```

配置済みのサンプルコードは次のリポジトリにあります。

https://github.com/yamamoto-yuta/qiita-dbt-osmosis-sample

今回、 dbt モデルは次のような構造で用意しました。

```mermaid
classDiagram
    class raw_orders {
        id
        user_id
        order_date
        status
    }

    class raw_customers {
        id
        first_name
        last_name
    }

    class stg_orders {
        order_id
        customer_id
        order_date
        status
    }

    class stg_customers {
        customer_id
        first_name
        last_name
    }

    class customers {
        customer_id
        first_name
        last_name
        first_order_date
        most_recent_order_date
        number_of_orders
    }

    %% 

    raw_orders --> stg_orders
    raw_customers --> stg_customers
    stg_orders --> customers : customer_id で JOIN
    stg_customers --> customers : customer_id で JOIN
```

:two: `dbt run` を実行

:::note
**Q. なぜ `dbt run` を実行するのか？**

dbt-osmosis は処理の中でデータベース上に実体化された dbt モデルの情報を参照します。そのため、一度データベース上に dbt モデルを実体化しておく必要があります。
:::

**試行1｜そのまま実行**

前準備のまま、何も手を加えず実行してみます。

実行コマンド:

```
dbt-osmosis yaml document
```

実行結果:

- `_source.yml` にカラム情報が追加されている
- `_schema.yml` は生成されていない

<details><summary>:memo: 実際の _source.yml と _schema.yml</summary>

```_source.yml
version: 2

sources:
  - name: <YOUR_DATASET>
    database: <YOUR_DATABASE>
    tables:
      - name: raw_customers
        columns:
          - name: id
            description: ''
            data_type: INT64
          - name: first_name
            description: ''
            data_type: STRING
          - name: last_name
            description: ''
            data_type: STRING
      - name: raw_orders
        columns:
          - name: id
            description: ''
            data_type: INT64
          - name: user_id
            description: ''
            data_type: INT64
          - name: order_date
            description: ''
            data_type: DATE
          - name: status
            description: ''
            data_type: STRING
```

```txt:_schema.yml
（存在しない）
```

</details>



**試行2｜_schema.ymlを用意して実行**

次のような `models/_schema.yml` を用意して実行してみます。

```_schema.yml
version: 2

models:
  - name: customers
```

実行コマンド:

```
dbt-osmosis yaml document
```

実行結果:

- `_schema.yml` が作成され、 `customers` モデルのカラム情報が記載されている

:::note
Document 機能では、最低限スキーマ YAML ファイルを作成し、 `- name: モデル名` まで書いてあげる必要があるようです。
:::

<details><summary>:memo: 実際の _schema.yml</summary>

```_schema.yml
version: 2

models:
  - name: customers
    columns:
      - name: customer_id
        description: ''
        data_type: INT64
      - name: first_name
        description: ''
        data_type: STRING
      - name: last_name
        description: ''
        data_type: STRING
      - name: first_order_date
        description: ''
        data_type: DATE
      - name: most_recent_order_date
        description: ''
        data_type: DATE
      - name: number_of_orders
        description: ''
        data_type: INT64
```

</details>

**試行3｜_schema.ymlに不要なカラムを追加してみる**

試行2の後、　`_schema.yml` に次の不要なカラムの情報を追記してコマンドを実行してみます。

```diff_yaml:_schma.yml
    version: 2
    
    models:
      - name: customers
        columns:
          - name: customer_id
            description: ""
            data_type: INT64

〜〜（略）〜〜

          - name: number_of_orders
            description: ""
            data_type: INT64
+         - name: hoge # 追加
```

実行コマンド:

```
dbt-osmosis yaml document
```

実行結果:

- 追加した `- name: hoge` が消えている

:::note
Document 機能の説明通り、データベースに存在しないカラムが YAML ファイルから削除されています。
:::

<details><summary>:memo: 実際の _schema.yml</summary>

```_schema.yml
version: 2

models:
  - name: customers
    columns:
      - name: customer_id
        description: ""
        data_type: INT64
      - name: first_name
        description: ""
        data_type: STRING
      - name: last_name
        description: ""
        data_type: STRING
      - name: first_order_date
        description: ""
        data_type: DATE
      - name: most_recent_order_date
        description: ""
        data_type: DATE
      - name: number_of_orders
        description: ""
        data_type: INT64
```

</details>

#### 参考

- [Document | CLI Overview | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/commands#document)
- [dbtプロジェクトのYAMLファイルを文書化 | dbt-osmosisを使ったメタデータ入力の軽減](https://zenn.dev/kyami/articles/d247a3b45f7044#dbt%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AEyaml%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E6%96%87%E6%9B%B8%E5%8C%96)

### Organize

#### 機能

> - dbt_project.yml ファイルの dbt-osmosis var に基づいて、ソースが存在しない場合はブートストラップします。
> - dbt_project.ymlファイルに設定されたdbt-osmosis config（理想的には）に基づいて、YAMLファイルを移行します。
> - プロジェクトが宣言的な仕様にマッチしていることを確認します（つまり、YAMLファイルが正しい場所にあり、正しい名前を持っている）。
> 
> ※ [公式ドキュメント](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/commands#document) を DeepL で翻訳したもの

```
dbt-osmosis yaml organize [--project-dir] [--profiles-dir] [--target]
```

#### 実際に試してみた


**前準備**


:one: 各種ファイルを配置

※ Document 機能を試した時と同じです。

配置済みのサンプルコード:

https://github.com/yamamoto-yuta/qiita-dbt-osmosis-sample

:two: `dbt run` を実行

**試行1｜そのまま実行**

前準備のまま、何も手を加えず実行してみます。

実行コマンド:

```
dbt-osmosis yaml organize
```

実行結果:

- `_schema.yml` が作成され、カラム情報が書き込まれている

<details><summary>:memo: 実際の _source.yml と _schema.yml</summary>

```txt:_source.yml
（変化なし）
```

```_schema.yml
version: 2
models:
  - name: stg_customers
    columns:
      - name: customer_id
        description: ''
      - name: first_name
        description: ''
      - name: last_name
        description: ''
  - name: customers
    columns:
      - name: customer_id
        description: ''
      - name: first_name
        description: ''
      - name: last_name
        description: ''
      - name: first_order_date
        description: ''
      - name: most_recent_order_date
        description: ''
      - name: number_of_orders
        description: ''
  - name: stg_orders
    columns:
      - name: order_id
        description: ''
      - name: customer_id
        description: ''
      - name: order_date
        description: ''
      - name: status
        description: ''
```

</details>





#### 参考

- [Organize | CLI Overview | dbt-osmosis Docs](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/commands/#organize)

### Refactor

#### 機能

> - dbt_project.yml ファイルの dbt-osmosis var に基づいて、ソースが存在しない場合はブートストラップします。
> - dbt_project.ymlファイルに設定されたdbt-osmosis config（理想的には）に基づいて、YAMLファイルを移行します。
> - プロジェクトが宣言的な仕様にマッチしていることを確認します（つまり、YAMLファイルが正しい場所にあり、正しい名前を持っている）。
> - YAMLファイルのカラムの並び順をデータベースのカラムの並び順と一致させます。
> - データベースに存在するカラムを YAML ファイルに追加する。
> - データベースに存在しないカラムをYAMLファイルから削除する
> - 上流モデルから下流モデルにカラムレベルのドキュメントを受け渡す (下流モデルにそのカラムのドキュメントがない場合)
> 
> このコマンドは、documentコマンドとorganizeコマンドを正しい順序で実行したものである。
> 
> ※ [公式ドキュメント](https://z3z1ma.github.io/dbt-osmosis/docs/tutorial-basics/commands#document) を DeepL で翻訳したもの

```
dbt-osmosis yaml refactor [--project-dir] [--profiles-dir] [--target]
```

#### 実際に試してみた

**前準備**

:one: 各種ファイルを配置

※ Document 機能を試した時と同じです。

配置済みのサンプルコード:

https://github.com/yamamoto-yuta/qiita-dbt-osmosis-sample

:two: `dbt run` を実行

**試行1｜そのまま実行**

前準備のまま、何も手を加えず実行してみます。

実行コマンド:

```
dbt-osmosis yaml refactor
```

実行結果:

- `_source.yml` にカラム情報が追加されている
- `_schema.yml` が自動で作られ、すべてのテーブルについてカラム情報が追加されている

<details><summary>:memo: 実際の _soruce.yml と _schema.yml</summary>

```_source.yml
version: 2

sources:
  - name: YOUR_GC_DATASET
    database: YOUR_GC_PROJECT_ID
    tables:
      - name: raw_customers
        columns:
          - name: id
            description: ''
            data_type: INT64
          - name: first_name
            description: ''
            data_type: STRING
          - name: last_name
            description: ''
            data_type: STRING
      - name: raw_orders
        columns:
          - name: id
            description: ''
            data_type: INT64
          - name: user_id
            description: ''
            data_type: INT64
          - name: order_date
            description: ''
            data_type: DATE
          - name: status
            description: ''
            data_type: STRING
```

```_schema.yml
version: 2
models:
  - name: stg_customers
    columns:
      - name: customer_id
        description: ''
        data_type: INT64
      - name: first_name
        description: ''
        data_type: STRING
      - name: last_name
        description: ''
        data_type: STRING
  - name: stg_orders
    columns:
      - name: order_id
        description: ''
        data_type: INT64
      - name: customer_id
        description: ''
        data_type: INT64
      - name: order_date
        description: ''
        data_type: DATE
      - name: status
        description: ''
        data_type: STRING
  - name: customers
    columns:
      - name: customer_id
        description: ''
        data_type: INT64
      - name: first_name
        description: ''
        data_type: STRING
      - name: last_name
        description: ''
        data_type: STRING
      - name: first_order_date
        description: ''
        data_type: DATE
      - name: most_recent_order_date
        description: ''
        data_type: DATE
      - name: number_of_orders
        description: ''
        data_type: INT64
```

</details>

**試行2｜カラム情報を伝搬させてみる**

`_schema.yml` に次の変更を加えて実行してみます。

```diff_yaml:_schema.yml
〜〜（略）〜〜

      - name: stg_customers
        columns:
          - name: customer_id
-           description: ''
+           description: 'hogehoge' # 編集
            data_type: INT64

〜〜（略）〜〜
```

実行コマンド:

```
dbt-osmosis yaml refactor
```

実行結果:

- `customers` モデルの `customer_id` カラム の description が `hogehoge` となっている
    - →伝搬してることがわかる

<details><summary>:memo: 実際の _schema.yml</summary>

```_schema.yml
version: 2
models:
  - name: stg_customers
    columns:
      - name: customer_id
        description: "hogehoge" # 編集
        data_type: INT64
      - name: first_name
        description: ""
        data_type: STRING
      - name: last_name
        description: ""
        data_type: STRING
  - name: stg_orders
    columns:
      - name: order_id
        description: ""
        data_type: INT64
      - name: customer_id
        description: ""
        data_type: INT64
      - name: order_date
        description: ""
        data_type: DATE
      - name: status
        description: ""
        data_type: STRING
  - name: customers
    columns:
      - name: customer_id
        description: "hogehoge"
        data_type: INT64
      - name: first_name
        description: ""
        data_type: STRING
      - name: last_name
        description: ""
        data_type: STRING
      - name: first_order_date
        description: ""
        data_type: DATE
      - name: most_recent_order_date
        description: ""
        data_type: DATE
      - name: number_of_orders
        description: ""
        data_type: INT64
```

</details>

## 結び

この記事では dbt-osmosis の標準機能について、実際の挙動をまとめました。記事に誤りなどありましたらご指摘いただけますと幸いです :bow:

標準機能の中で最もよく使うのは Refactor かと思います。次の記事で Refactor のコマンドオプションについてもまとめていますので、よろしければご覧ください。

https://qiita.com/yamamoto-yuta/items/df2add3a8cdeae131ec2

ここまでお読みいただきありがとうございました :bow: