🕸

SQLMesh入門

に公開

はじめに

周回遅れではありますが、モダンデータスタックを始めたいと思います。
モダンデータスタックの中でも、ELT の T の部分は dbt 一択のようなので、dbt 入門してみます。

ということで、4月に「dbt入門」という記事を書きました。

そして、5/29 の dbt-fusion の発表を聞いて、Rust で書き直すみたいだから、外部仕様からにしようと「dbt Artifactsから見たdbt」という記事も書きながら、dbt-fusion を待っていました。
もう、4ヶ月になるから、そろそろ dbt-fusion のソースコード でも読んでみるかと、(Rust を一夜漬けで勉強して)読んでみましたが…。(本当は、その Rust の勉強も dbt-fusion のソースコード解析でも記事を書こうと思っていたのですが)

https://github.com/dbt-labs/dbt-fusion/blob/main/crates/dbt-sa-cli/src/dbt_sa_clap.rs#L68-L90

あれれれ、build とか run とか無いの?

とはいえ、冒頭でも書いたように「T の部分は dbt 一択」なので、(記事)どうしようかなと(心が)フラフラしていたところ、見つけちゃいました。

https://www.tobikodata.com/sqlmesh

It is more than just a dbt alternative.

やるしかない。

用語と概念

dbt との違いについては、(よく知らないのに勝手なことを書くと信者のみなさまに叱られるので)公式の FAQ などを見てもらえればと思います。

プロジェクト(リポジトリ?)

プロジェクトについては、Concepts にないので、わかりにくかったのですが、Guide は、あります。
要するに、sqlmesh init で作られるような SQLMesh 用の資材を置く単位のようです。config.yaml または config.py の単位と考える方が良いのかもしれません。そう考えると、この構成ファイルで定義される構成の範囲がプロジェクトと言えるかもしれません。

  1. プロジェクト - SQLMesh プロジェクト ディレクトリの構成オプション。
  2. 環境 - SQLMesh 環境の作成/昇格、物理テーブル スキーマ、およびビュー スキーマの構成オプション。
  3. ゲートウェイ - SQLMesh がデータ ウェアハウス、状態バックエンド、およびスケジューラに接続する方法の構成オプション。
  4. ゲートウェイ/接続のデフォルト - ゲートウェイまたは接続がすべて明示的に指定されていない場合の動作に関する構成オプション。
  5. モデルのデフォルト - モデル固有の構成がモデルのファイルで明示的に指定されていない場合の動作に関する構成オプション。
  6. デバッグモード - SQLMesh がアクションと完全なバックトレースを出力およびログに記録するための構成オプション。

リポジトリ?と書いたのは、Multi-Repo guideでは、-p 指定する プロジェクトリポジトリ と呼んでいるので、そういう用語の使われ方をしている場合もあるかも?という感じです。

プラン

プランとは、プロジェクトのローカル状態とターゲットの環境の状態の差異をまとめた変更セットです。モデルの変更をターゲット環境に反映させるには、プランを作成して適用する必要があります。

ということなので、SQLMesh で作成・変更した定義をデータウェアハウスに反映させるには、sqlmesh plan をすることになります。

環境

環境は、変更内容をテストおよびプレビューできる独立した名前空間です。

環境が変われば、名前空間が変わるので、同じ名前のまま、本番環境と開発環境が作れるということになります。

また、sqlmesh plan は、本番環境 prod 以外の環境では、コマンド引数で環境名を指定しますが、上記のプランの定義から、対象となるローカルのプロジェクトの状態を対象の環境が指す状態とデータウェアハウスに反映させるという関係になります。

状態

SQLMesh は、プロジェクトに関する情報を状態データベースに保存します。このデータベースは通常、メインのウェアハウスとは別のものです。

SQLMesh 状態データベースには、以下の情報が含まれるそうです。

  • プロジェクト内のすべての モデルバージョン に関する情報 (クエリ、読み込み間隔、依存関係)
  • プロジェクト内のすべての 仮想データ環境 のリスト
  • 各 仮想データ環境 に 昇格 されているモデルバージョン
  • プロジェクト内に存在する 自動再ステートメント に関する情報
  • 現在の SQLMesh / SQLGlot バージョンなど、プロジェクトに関するその他のメタデータ

ここで、モデルではなく、モデルバージョンと言っているのは、スナップショットだからだと思います。

モデル

モデルは、テーブルとビューを作成するメタデータとクエリで構成され、他のモデルやSQLMeshの外部でも使用できます。モデルはSQLMeshプロジェクトのmodels/ディレクトリに定義され、.sqlファイルに格納されます。

モデルには、以下の種類があるようです。

  • INCREMENTAL_BY_TIME_RANGE
    INCREMENTAL_BY_TIME_RANGE タイプのモデルは、時間範囲に基づいて増分的に計算されます。これは、レコードが時間の経過とともにキャプチャされ、イベント、ログ、トランザクションなどの不変のファクトを表すデータセットに最適な選択肢です。適切なデータセットにこのタイプを使用すると、通常、コストと時間を大幅に節約できます。
  • INCREMENTAL_BY_UNIQUE_KEY
    INCREMENTAL_BY_UNIQUE_KEY タイプのモデルは、キーに基づいて増分的に計算されます。
  • FULL
    FULL 型のモデルでは、モデルに関連付けられたデータセットは、モデル評価のたびに完全に更新(書き換え)されます。
  • VIEW
    VIEW 型のモデルは、モデルクエリの出力がマテリアライズされ、物理テーブルに保存されるのではなく、モデルのクエリに基づいて、非マテリアライズドビュー(または「仮想テーブル」)が作成または置換されます。
  • EMBEDDED
    埋め込みモデルは、異なる種類のモデル間で共通のロジックを共有する方法です。
  • SEED
    SEED モデルの種類は、SQLMesh プロジェクトで静的 CSV データセットを使用するための シード モデル を指定するために使用されます。
  • SCD Type 2
    SCD Type 2 は、SQLMesh プロジェクトで 緩やかに変化するディメンション (SCD) をサポートするモデル種別です。SCD はデータウェアハウスでよく使用されるパターンで、レコードの変更を時間経過とともに追跡できます。
    SQLMesh は、モデルに valid_from 列と valid_to 列を追加することでこれを実現します。
  • EXTERNAL
    EXTERNALモデル種別は、外部テーブルに関するメタデータを格納する外部モデルを指定するために使用されます。外部モデルは特別なモデルであり、他のモデル種別のように.sqlファイルで指定されません。オプションですが、SQLMeshプロジェクトでクエリされた外部テーブルの列と型情報を伝播するのに役立ちます。
  • MANAGED(開発中)
    MANAGED モデルは、基盤となるデータベースエンジンがデータのライフサイクルを管理するモデルを作成するために使用されます。
    Snowflake の Dynamic tables を想定しているようです(知らんけど)
  • INCREMENTAL_BY_PARTITION
    INCREMENTAL_BY_PARTITION タイプのモデルは、パーティションに基づいて増分的に計算されます。列のセットはモデルのパーティションキーを定義し、パーティションは同じパーティションキー値を持つ行のグループです。
  • INCREMENTAL_UNMANAGED
    INCREMENTAL_UNMANAGED モデルは、追加専用テーブルをサポートするために存在します。これは、SQLMesh がデータのロード方法を管理しないという意味で「非管理」です。SQLMesh は設定された頻度でクエリを実行し、取得したデータをテーブルに追加します。
    Data Vault などのデータ管理パターンでは、追加専用のテーブルが使用される場合があります(知らんけど)

スナップショット

スナップショットとは、特定の時点におけるモデルの記録です。スナップショットには、モデルのコピーに加えて、モデルを評価してクエリをレンダリングするために必要なすべての情報が含まれています。これにより、SQLMesh は、プロジェクトとそのモデルが進化・変化しても、プロジェクトの履歴とデータを一貫した方法で把握できます。モデルクエリにはマクロが含まれる場合があるため、各スナップショットには、スナップショット作成時点のすべてのマクロ定義とグローバル変数のコピーが保存されます。さらに、スナップショットにはデータが存在する期間も保存されます。

やってみた

ま、やってみましょう。(uv や duckdb については、以前の記事を参照してください)

Get started

これです。

準備

uv init sqlmesh-example
cd sqlmesh-example
uv venv
uv add sqlmesh

プロジェクト作成

uv run sqlmesh init duckdb
tree .

無言で終わる(ように引数つけた)ので、プロジェクトの中身をみておきます(uv init の分も入ってますが)。

.
├── README.md
├── audits
│   └── assert_positive_order_ids.sql
├── config.yaml
├── logs
│   └── sqlmesh_2025_09_24_06_32_07.log
├── macros
│   └── __init__.py
├── main.py
├── models
│   ├── full_model.sql
│   ├── incremental_model.sql
│   └── seed_model.sql
├── pyproject.toml
├── seeds
│   └── seed_data.csv
├── tests
│   └── test_full_model.yaml
└── uv.loc

マクロ以外はサンプルを作ってくれてます。

本番環境作成

sqlmesh initduckdb を指定したので、サンプルで定義されたいるものを duckdb に作成します。

$ uv run sqlmesh plan
.
======================================================================
Successfully Ran 1 tests against duckdb in 0.09 seconds.
----------------------------------------------------------------------
Initializing new project state...

`prod` environment will be initialized

Models:
└── Added:
    ├── sqlmesh_example.full_model
    ├── sqlmesh_example.incremental_model
    └── sqlmesh_example.seed_model
Models needing backfill:
├── sqlmesh_example.full_model: [full refresh]
├── sqlmesh_example.incremental_model: [2020-01-01 - 2025-09-23]
└── sqlmesh_example.seed_model: [full refresh]
Apply - Backfill Tables [y/n]: 

テスト成功したよ。モデル3つ足したよ。バックフィルする(足らん分埋めとく)?と言っているので、y します。

Apply - Backfill Tables [y/n]: y
/home/ec2-user/work/sqlmesh-work/sqlmesh-example/.venv/lib/python3.13/site-packages/sqlmesh/core/model/definition.py:1684: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. You can safely remove this argument.
  df[column] = pd.to_datetime(df[column], infer_datetime_format=True, errors="ignore")  # type: ignore 
/home/ec2-user/work/sqlmesh-work/sqlmesh-example/.venv/lib/python3.13/site-packages/sqlmesh/core/model/definition.py:1684: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_datetime without passing `errors` and catch exceptions explicitly instead
  df[column] = pd.to_datetime(df[column], infer_datetime_format=True, errors="ignore")  # type: ignore
[1/1] sqlmesh_example.seed_model          [insert seed file]                   0.04s
[1/1] sqlmesh_example.incremental_model   [insert 2020-01-01 - 2025-09-23]     0.02s
[1/1] sqlmesh_example.full_model          [full refresh, audits ✔1]           0.04s
Executing model batches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 3/3 • 0:00:00

✔ Model batches executed

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 3/3 • 0:00:00

✔ Virtual layer updated

製品サイトのドキュメントと違って、警告が出てますが、バックフィルできたようです。テキストのように duckdb の中身も見ておきます。

$ duckdb db.db
v1.2.1 8e52ec4395
Enter ".help" for usage hints.
D select schema_name, table_name from duckdb_tables() where schema_name='sqlmesh__sqlmesh_example';
┌──────────────────────────┬─────────────────────────────────────────────┐
│       schema_name        │                 table_name                  │
│         varchar          │                   varchar                   │
├──────────────────────────┼─────────────────────────────────────────────┤
│ sqlmesh__sqlmesh_example │ sqlmesh_example__full_model__3532441660     │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__incremental_model__2019538 │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__seed_model__3896887493     │
└──────────────────────────┴─────────────────────────────────────────────┘
D select schema_name, view_name from duckdb_views() where schema_name='sqlmesh_example';
┌─────────────────┬───────────────────┐
│   schema_name   │     view_name     │
│     varchar     │      varchar      │
├─────────────────┼───────────────────┤
│ sqlmesh_example │ full_model        │
│ sqlmesh_example │ incremental_model │
│ sqlmesh_example │ seed_model        │
└─────────────────┴───────────────────┘

本番環境は、モデルに指定されたスキーマ名、モデル名のビューとして作られ、実態のテーブルは sqlmesh 管理のスキーマにある感じでしょうか。

モデル更新

models/incremental_model.sql をテキストのとおり変更します。

開発環境作成

$ uv run sqlmesh plan dev
.
======================================================================
Successfully Ran 1 tests against duckdb in 0.04 seconds.
----------------------------------------------------------------------

New environment `dev` will be created from `prod`


Differences from the `prod` environment:

Models:
├── Directly Modified:
│   └── sqlmesh_example__dev.incremental_model
└── Indirectly Modified:
    └── sqlmesh_example__dev.full_model

Metadata Updated: sqlmesh_example__dev.full_model

---                                                                                   
                                                                                      
+++                                                                                   
                                                                                      
@@ -15,6 +15,7 @@                                                                     
                                                                                      
 SELECT                                                                               
   id,                                                                                
   item_id,                                                                           
+  'z' AS new_column,                                                                 
   event_date                                                                         
 FROM sqlmesh_example.seed_model                                                      
 WHERE                                                                                

Directly Modified: sqlmesh_example__dev.incremental_model (Non-breaking)
└── Indirectly Modified Children:
    └── sqlmesh_example__dev.full_model (Indirect Non-breaking)
Models needing backfill:
└── sqlmesh_example__dev.incremental_model: [2020-01-01 - 2025-09-23]
Apply - Backfill Tables [y/n]: y
[1/1] sqlmesh_example__dev.incremental_model   [insert 2020-01-01 - 2025-09-23]       
0.02s   
Executing model batches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 1/1 • 0:00:00
                                                                                      
✔ Model batches executed

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 2/2 • 0:00:00

✔ Virtual layer updated

モデルの変更とその内容、影響範囲を検知して、バックフィルしてくれています。一個前のステップでも気が付いていたのですが、テキストと違って ✔ Physical layer updated は、出力されていません。

開発環境で更新を検証

ここもテキストのとおり、実施しておきます。

$ uv run sqlmesh fetchdf "select * from sqlmesh_example__dev.incremental_model"
   id  item_id new_column event_date
0   1        2          z 2020-01-01
1   2        1          z 2020-01-01
2   3        3          z 2020-01-03
3   4        1          z 2020-01-04
4   5        1          z 2020-01-05
5   6        1          z 2020-01-06
6   7        1          z 2020-01-07

$ uv run sqlmesh fetchdf "select * from sqlmesh_example.incremental_model"
   id  item_id event_date
0   1        2 2020-01-01
1   2        1 2020-01-01
2   3        3 2020-01-03
3   4        1 2020-01-04
4   5        1 2020-01-05
5   6        1 2020-01-06
6   7        1 2020-01-07

テキストでは実施していませんが、duckdb も見ておきます。

$ duckdb db.db
v1.2.1 8e52ec4395
Enter ".help" for usage hints.
D select schema_name, table_name from duckdb_tables() where schema_name='sqlmesh__sqlmesh_example';
┌──────────────────────────┬───────────────────────────────────────────────┐
│       schema_name        │                  table_name                   │
│         varchar          │                    varchar                    │
├──────────────────────────┼───────────────────────────────────────────────┤
│ sqlmesh__sqlmesh_example │ sqlmesh_example__full_model__3532441660       │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__incremental_model__2019538   │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__incremental_model__639040956 │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__seed_model__3896887493       │
└──────────────────────────┴───────────────────────────────────────────────┘
D select schema_name, view_name from duckdb_views() where schema_name='sqlmesh_example';
┌─────────────────┬───────────────────┐
│   schema_name   │     view_name     │
│     varchar     │      varchar      │
├─────────────────┼───────────────────┤
│ sqlmesh_example │ full_model        │
│ sqlmesh_example │ incremental_model │
│ sqlmesh_example │ seed_model        │
└─────────────────┴───────────────────┘
D select schema_name, view_name from duckdb_views() where schema_name='sqlmesh_example__dev';
┌──────────────────────┬───────────────────┐
│     schema_name      │     view_name     │
│       varchar        │      varchar      │
├──────────────────────┼───────────────────┤
│ sqlmesh_example__dev │ full_model        │
│ sqlmesh_example__dev │ incremental_model │
└──────────────────────┴───────────────────┘

物理レイヤには、今回変更した sqlmesh_example__incremental_model__639040956 が追加され、select で検証したとおり、sqlmesh_example__dev スキーマに view が追加されています。ここで、seed_model は、開発環境にはできないのだなぁ。という気づきがありました。

本番環境へ更新の適用

ま、想定どおりです。

$ uv run sqlmesh plan
.
======================================================================
Successfully Ran 1 tests against duckdb in 0.04 seconds.
----------------------------------------------------------------------

Differences from the `prod` environment:

Models:
├── Directly Modified:
│   └── sqlmesh_example.incremental_model
└── Indirectly Modified:
    └── sqlmesh_example.full_model

Metadata Updated: sqlmesh_example.full_model

---                                                                                   
                                                                                      
+++                                                                                   
                                                                                      
@@ -15,6 +15,7 @@                                                                     
                                                                                      
 SELECT                                                                               
   id,                                                                                
   item_id,                                                                           
+  'z' AS new_column,                                                                 
   event_date                                                                         
 FROM sqlmesh_example.seed_model                                                      
 WHERE                                                                                

Directly Modified: sqlmesh_example.incremental_model (Non-breaking)
└── Indirectly Modified Children:
    └── sqlmesh_example.full_model (Indirect Non-breaking)
Apply - Virtual Update [y/n]: y

SKIP: No physical layer updates to perform

SKIP: No model batches to execute

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 2/2 • 0:00:00

✔ Virtual layer updated

本番環境での更新の検証

こちらも想定どおり

$ uv run sqlmesh fetchdf "select * from sqlmesh_example.incremental_model"
   id  item_id new_column event_date
0   1        2          z 2020-01-01
1   2        1          z 2020-01-01
2   3        3          z 2020-01-03
3   4        1          z 2020-01-04
4   5        1          z 2020-01-05
5   6        1          z 2020-01-06
6   7        1          z 2020-01-07

テキストにはありませんが、念のため duckdb も見ておきます。

$ duckdb db.db
v1.2.1 8e52ec4395
Enter ".help" for usage hints.
D select schema_name, table_name from duckdb_tables() where schema_name='sqlmesh__sqlmesh_example';
┌──────────────────────────┬───────────────────────────────────────────────┐
│       schema_name        │                  table_name                   │
│         varchar          │                    varchar                    │
├──────────────────────────┼───────────────────────────────────────────────┤
│ sqlmesh__sqlmesh_example │ sqlmesh_example__full_model__3532441660       │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__incremental_model__2019538   │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__incremental_model__639040956 │
│ sqlmesh__sqlmesh_example │ sqlmesh_example__seed_model__3896887493       │
└──────────────────────────┴───────────────────────────────────────────────┘

SKIP: No physical layer updates to perform と言ってましたからね。想像どおり、最初のテーブルは消してないです。

プロジェクトプラン環境については、なんとなくイメージできるようになったのではないでしょうか。

SQLMesh CLI Crash Course

これです。

準備

git clone https://github.com/sungchun12/sqlmesh-cli-crash-course.git
cd sqlmesh-cli-crash-course/
uv init
uv venv
uv add sqlmesh

材料を確認しておきます。

$ tree .
.
├── README.md
├── audits
│   └── assert_positive_order_ids.sql
├── config.yaml
├── macros
│   └── __init__.py
├── main.py
├── models
│   ├── full_model.sql
│   ├── incremental_by_partition.sql
│   ├── incremental_by_unique_key.sql
│   ├── incremental_model.sql
│   ├── seed_model.sql
│   └── view_model.sql
├── pyproject.toml
├── seeds
│   └── seed_data.csv
├── tests
└── uv.lock

そして、README.md に書かれているように初期化します。

$ uv run sqlmesh plan
[WARNING] Linter warnings for /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/seed_model.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
[WARNING] Linter warnings for /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/incremental_by_partition.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
[WARNING] Linter warnings for /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/incremental_by_unique_key.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
Initializing new project state...

`prod` environment will be initialized

Models:
└── Added:
    ├── sqlmesh_example.full_model
    ├── sqlmesh_example.incremental_model
    ├── sqlmesh_example.incremental_unique_model
    ├── sqlmesh_example.seed_model
    ├── sqlmesh_example.view_model
    └── sqlmesh_example_v3.incremental_partition_model
Models needing backfill:
├── sqlmesh_example.full_model: [full refresh]
├── sqlmesh_example.incremental_model: [2020-01-01 - 2025-09-23]
├── sqlmesh_example.incremental_unique_model: [2020-01-01 - 2025-09-23]
├── sqlmesh_example.seed_model: [full refresh]
├── sqlmesh_example.view_model: [recreate view]
└── sqlmesh_example_v3.incremental_partition_model: [2020-01-01 - 2025-09-23]
Apply - Backfill Tables [y/n]: y
/home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/.venv/lib/python3.12/site-packages/sqlmesh/core/model/definition.py:1684: UserWarning: The argument 'infer_datetime_format' is deprecated and will be removed in a future version. A strict version of it is now the default, see https://pandas.pydata.org/pdeps/0004-consistent-to-datetime-parsing.html. 
You can safely remove this argument.
  df[column] = pd.to_datetime(df[column], infer_datetime_format=True, errors="ignore")  # type: ignore
/home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/.venv/lib/python3.12/site-packages/sqlmesh/core/model/definition.py:1684: FutureWarning: errors='ignore' is deprecated and will raise in a future version. Use to_datetime without passing `errors` and catch exceptions explicitly instead
  df[column] = pd.to_datetime(df[column], infer_datetime_format=True, errors="ignore")  # type: ignore
[1/1] sqlmesh_example.seed_model                 [insert seed file]                0.04s   
[1/1] sqlmesh_example.incremental_unique_model   [insert/update rows]              0.03s   
[1/1] sqlmesh_example.incremental_model          [insert 2020-01-01 - 2025-09-23, audits ✔2]   0.04s   

[WARNING] sqlmesh_example.full_model: 'assert_positive_order_ids' audit error: 2 rows failed. Learn more in logs: /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/logs/sqlmesh_2025_09_24_10_11_58.log

[1/1] sqlmesh_example.full_model                 [full refresh, audits ❌1]       0.03s   

[WARNING] sqlmesh_example.view_model: 'assert_positive_order_ids' audit error: 2 rows failed. Learn more in logs: /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/logs/sqlmesh_2025_09_24_10_11_58.log

[1/1] sqlmesh_example.view_model                 [recreate view, audits ✔2 ❌1]   0.04s   
Executing model batches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 5/5 • 0:00:00                                
                                                                                                                       
✔ Model batches executed

[1/1] sqlmesh_example_v3.incremental_partition_model   [insert partitions]        0.03s   
Executing model batches ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 1/1 • 0:00:00                                
                                                                                                                       
✔ Model batches executed

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 6/6 • 0:00:00

✔ Virtual layer updated

あ、警告や audit エラーが出るパターンみたいですね。テキストに書いてあるように、何も変更しないで sqlmesh plan dev すると、以下のように叱られます。

$ uv run sqlmesh plan dev
[WARNING] Linter warnings for 
/home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/incremental_by_partition.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
[WARNING] Linter warnings for /home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/seed_model.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
[WARNING] Linter warnings for 
/home/ec2-user/work/sqlmesh-work/sqlmesh-cli-crash-course/models/incremental_by_unique_key.sql:
 - Line 1: nomissingaudits - Model `audits` must be configured to test data quality.
Error: Creating a new environment requires a change, but project files match the `prod` environment. Make a change or use the --include-unmodified flag to create a new environment without changes.

開発環境での変更のプレビュー、適用、監査

このサンプルの期待している使い方とは違うかもしれませんが、修復してみます。

Linter warnings

まずは、3つの [WARNING] Linter warnings からやります。そもそも、このサンプルも初期化時に作られるものの延長です。もちろん、初期化時のサンプルでは警告は出力されないので、なにかが変更されているはずです。

https://github.com/sungchun12/sqlmesh-cli-crash-course/blob/main/config.yaml

16行目の warn_rules: ["noselectstar", "nomissingaudits"] が追加されているからです。nomissingaudits は、ここに説明がありました。

SQLMesh は、データ品質をテストするための audits をモデルの構成内に見つけることができませんでした。

ということなので、3つのモデルに追加してみます。今回は変更ありなので、sqlmesh plan dev します。

$ uv run sqlmesh plan dev

New environment `dev` will be created from `prod`


Differences from the `prod` environment:

Models:
└── Metadata Updated:
    ├── sqlmesh_example__dev.incremental_unique_model
    ├── sqlmesh_example__dev.seed_model
    └── sqlmesh_example_v3__dev.incremental_partition_model

---                                                                                                            
                                                                                                               
+++                                                                                                            
                                                                                                               
@@ -10,6 +10,9 @@                                                                                              
                                                                                                               
     on_destructive_change 'ERROR',                                                                            
     on_additive_change 'ALLOW'                                                                                
   ),                                                                                                          
+  audits (NOT_NULL('columns' = (                                                                              
+      id                                                                                                      
+  ))),                                                                                                        
   grains ((id, event_date))                                                                                   
 )                                                                                                             
 SELECT                                                                                                        

Metadata Updated: sqlmesh_example__dev.incremental_unique_model

---                                                                                                            
                                                                                                               
+++                                                                                                            
                                                                                                               
@@ -11,5 +11,8 @@                                                                                              
                                                                                                               
     item_id INT,                                                                                              
     event_date DATE                                                                                           
   ),                                                                                                          
+  audits (NOT_NULL('columns' = (                                                                              
+      id                                                                                                      
+  ))),                                                                                                        
   grains ((id, event_date))                                                                                   
 )                                                                                                             

Metadata Updated: sqlmesh_example__dev.seed_model

---                                                                                                            
                                                                                                               
+++                                                                                                            
                                                                                                               
@@ -9,6 +9,9 @@                                                                                                
                                                                                                               
     on_additive_change 'ALLOW'                                                                                
   ),                                                                                                          
   partitioned_by "id",                                                                                        
+  audits (NOT_NULL('columns' = (                                                                              
+      id                                                                                                      
+  ))),                                                                                                        
   grains ((id, event_date))                                                                                   
 )                                                                                                             
 SELECT                                                                                                        

Metadata Updated: sqlmesh_example_v3__dev.incremental_partition_model
Apply - Virtual Update [y/n]: y

SKIP: No physical layer updates to perform

[1/1] sqlmesh_example__dev.incremental_unique_model         [insert/update rows, audits ✔1]                    
0.01s   
[1/1] sqlmesh_example_v3__dev.incremental_partition_model   [insert partitions, audits ✔1]                     
0.01s   
Auditing models ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 2/2 • 0:00:00                                
                                                                                                               
✔ Model batches executed

SKIP: No model batches to execute

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 6/6 • 0:00:00

✔ Virtual layer updated

3か所とも変更箇所も表示されていますね。冒頭の3か所の警告が消えて、プロジェクトのローカルの状態を変更したのでエラーとならず実行されました。

本番とのデータ比較

どうもテキストとリポジトリの内容がずれているような気がしますが、データ比較をするということなので、やっておきます。

$ uv run sqlmesh table_diff prod:dev -m '*' --show-sample
No models contain differences with the selection criteria: '*'

ま、出力に影響のない変更しかしてませんから。

本番環境への変更の適用

変更内容に問題がなければ、prod に適用します。

ということなので、適用します。dev に適用した際に、本番との差分が表示されて適用しましたが、今度は、前回の本番と今回の本番の差分になるので、同じ内容が表示されます。

$ uv run sqlmesh plan

Differences from the `prod` environment:

Models:
└── Metadata Updated:
    ├── sqlmesh_example.incremental_unique_model
    ├── sqlmesh_example.seed_model
    └── sqlmesh_example_v3.incremental_partition_model

---                                                                                                 
                                                                                                    
+++                                                                                                 
                                                                                                    
@@ -10,6 +10,9 @@                                                                                   
                                                                                                    
     on_destructive_change 'ERROR',                                                                 
     on_additive_change 'ALLOW'                                                                     
   ),                                                                                               
+  audits (NOT_NULL('columns' = (                                                                   
+      id                                                                                           
+  ))),                                                                                             
   grains ((id, event_date))                                                                        
 )                                                                                                  
 SELECT                                                                                             

Metadata Updated: sqlmesh_example.incremental_unique_model

---                                                                                                 
                                                                                                    
+++                                                                                                 
                                                                                                    
@@ -11,5 +11,8 @@                                                                                   
                                                                                                    
     item_id INT,                                                                                   
     event_date DATE                                                                                
   ),                                                                                               
+  audits (NOT_NULL('columns' = (                                                                   
+      id                                                                                           
+  ))),                                                                                             
   grains ((id, event_date))                                                                        
 )                                                                                                  

Metadata Updated: sqlmesh_example.seed_model

---                                                                                                 
                                                                                                    
+++                                                                                                 
                                                                                                    
@@ -9,6 +9,9 @@                                                                                     
                                                                                                    
     on_additive_change 'ALLOW'                                                                     
   ),                                                                                               
   partitioned_by "id",                                                                             
+  audits (NOT_NULL('columns' = (                                                                   
+      id                                                                                           
+  ))),                                                                                             
   grains ((id, event_date))                                                                        
 )                                                                                                  
 SELECT                                                                                             

Metadata Updated: sqlmesh_example_v3.incremental_partition_model
Apply - Virtual Update [y/n]: y

SKIP: No physical layer updates to perform

SKIP: No model batches to execute

Updating virtual layer  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 6/6 • 0:00:00

✔ Virtual layer updated

おわりに

SQLMesh なかなか良さそうです。
ただ、上記のとおり、公式サイトを含めて、サンプルやトレーニング用の教材が少なく、作者の意図がわかりにくいように思います。
日本語の情報もほとんどなかったので、この記事が少しでもお役に立てば幸いです。(もう少し触ってみようと思うので、続編を書くかもしれません)

2025/9/3 に以下が発表されています。

https://www.fivetran.com/press/fivetran-acquires-tobiko-data-to-power-the-next-generation-of-advanced-ai-ready-data-transformation

これをきっかけに SQLMesh が dbt 一強の状態を変えていくかもしれませんね。

GitHubで編集を提案
株式会社ROBONの技術ブログ

Discussion