- Overview
- References
- Introduction to the examples in this Git repository
- Quickstart
- More advanced examples
- Installation
Created by gh-md-toc
This cheat sheet explains how to install and to use SQLMesh, e.g., on a laptop or on a virtual machine (VM).
SQLMesh is a next-generation data transformation framework designed to ship data quickly, efficiently, and without error. Data teams can efficiently run and deploy data transformations written in SQL or Python with visibility and control at any size. It is more than just a dbt alternative.
SQLMesh requires some database to store its state. DuckDB is the database by default, as it is small and efficient enough to be available virtually everywhere:
DuckDB is an embedded database, similar to SQLite, but designed for OLAP-style analytics. It is crazy fast and allows you to read and write data stored in CSV, JSON, and Parquet files directly, without requiring you to load them into the database first.
For production-ready deployments, other database backends, like PostgreSQL, may be advised.
- Data Engineering Helpers - Knowledge Sharing - Python
- Data Engineering Helpers - Knowledge Sharing - DuckDB
- Data Engineering Helpers - Knowledge Sharing - PostgreSQL
- Data Engineering Helpers - Knowledge Sharing - Unity Catalog (UC)
- Data Engineering Helpers - Knowledge Sharing - Spark
- Data Engineering Helpers - Knowledge Sharing - dbt
- Data Engineering Helpers - Knowledge Sharing - Airflow
- Material for the Data platform - Data life cycle
- Material for the Data platform - Modern Data Stack (MDS) in a box
- Title: How to create an SCD2 Table using MERGE INTO with Spark & Iceberg
- Date: Apr. 2025
- Link to the article: https://www.startdataengineering.com/post/create-scd2-table-with-merge-into-with-spark-iceberg/
- Wikipedia article: https://en.wikipedia.org/wiki/Slowly_changing_dimension
- GitHub page: https://github.com/TobikoData/sqlmesh
- Home page: https://sqlmesh.com/
- Company behind it: Tobiko Data
- Some significant contributors:
- CEO of Tobyko: Tobias Mao, aka Toby (Toby Mao on LinkedIn, Toby Mao on GitHub, Toby Mao on Tobiko blog site, Toby Mao home page)
- Co-founder of Tobiko: Iaroslav Zeigerman (Iaroslav Zeigerman on LinkedIn, Iaroslav Zeigerman on GitHub, Iaroslav Zeigerman on the Tobiko blog site)
- Founding engineer: Ryan Eakman (Ryan Eakman on LinkedIn, Ryan Eakman on GitHub, Ryan Eakman on Tobiko blog site)
- Related project:
- SQLGlot: https://github.com/tobymao/sqlglot (and SQLGlot home page)
- Blog: https://tobikodata.com/blog
- Quickstart guide: https://sqlmesh.readthedocs.io/en/stable/quick_start/
- Walkthrough example: https://sqlmesh.readthedocs.io/en/stable/examples/incremental_time_full_walkthrough/
- Overview: https://sqlmesh.readthedocs.io/en/stable/concepts/overview/
- Glossary: https://sqlmesh.readthedocs.io/en/stable/concepts/glossary/
- Plans: https://sqlmesh.readthedocs.io/en/stable/concepts/plans/
- Environments: https://sqlmesh.readthedocs.io/en/stable/concepts/environments/
- Testing: https://sqlmesh.readthedocs.io/en/stable/concepts/tests/
- Auditing: https://sqlmesh.readthedocs.io/en/stable/concepts/audits/
- Overview: https://sqlmesh.readthedocs.io/en/stable/concepts/models/overview/
- Model kinds:
- SQLMesh doc: https://sqlmesh.readthedocs.io/en/stable/concepts/models/model_kinds/
- Wikipedia on Slow Changing Dimension (SCD) models: https://en.wikipedia.org/wiki/Slowly_changing_dimension
- SQL models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/sql_models/
- Python models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/
- Seed models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/seed_models/
- External models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/external_models/
- Managed models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/managed_models/
- Overview: https://sqlmesh.readthedocs.io/en/stable/concepts/macros/overview/
- Macro variables: https://sqlmesh.readthedocs.io/en/stable/concepts/macros/macro_variables/
- SQLMesh variables: https://sqlmesh.readthedocs.io/en/stable/concepts/macros/sqlmesh_macros/
- Jinja macros: https://sqlmesh.readthedocs.io/en/stable/concepts/macros/jinja_macros/
- Overview: https://sqlmesh.readthedocs.io/en/stable/concepts/metrics/overview/
- Definition: https://sqlmesh.readthedocs.io/en/stable/concepts/metrics/definition/
- Snapshots: https://sqlmesh.readthedocs.io/en/stable/concepts/architecture/snapshots/
- Serialization: https://sqlmesh.readthedocs.io/en/stable/concepts/architecture/serialization/
- Integration with Airflow: https://sqlmesh.readthedocs.io/en/stable/integrations/airflow/
- Integration with GitHub Actions CI/CD bot: https://sqlmesh.readthedocs.io/en/stable/integrations/github/
- Integration with dbt: https://sqlmesh.readthedocs.io/en/stable/integrations/dbt/
- Integration with dlt: https://sqlmesh.readthedocs.io/en/stable/integrations/dlt/
- Integration with Spark: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/spark/
- Integration with DataBricks: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/databricks/
- Integration with Athena: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/athena/
- Integration with DuckDB: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/duckdb/
- Integration with PostgreSQL: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/postgres/
- Git repository: https://github.com/TobikoData/sqlmesh-examples
- Jupyter notebook about the Sushi project: https://github.com/TobikoData/sqlmesh-examples/blob/main/001_sushi/sushi-overview.ipynb
- Ibis SQLMesh project directory: https://github.com/TobikoData/sqlmesh-examples/tree/main/002_ibis
- The Ibis SQLMesh project features:
- Python models: https://github.com/TobikoData/sqlmesh-examples/tree/main/002_ibis/models
- The Ibis framework: https://github.com/ibis-project/ibis
- Home page: https://duckdb.org/
- Home page: https://www.unitycatalog.io
- GitHub page: https://github.com/unitycatalog/unitycatalog
- Unity Catalog docs
- Unity Catalog blog post - Integrating Spark with Unity Catalog via Open APIs
- Triggered by Tobias Mao, aka Toby: https://www.reddit.com/r/dataengineering/comments/124tspm/sqlmesh_the_future_of_dataops/
-
Overview: series of articles to explore SQLMesh and how it compares to dbt
-
Author: David Jayatillake (David Jayatillake on LinkedIn, David Jayatillake on Substack)
-
Date: Dec. 2024
-
Publisher: Substack
-
Link to the articles:
- Migrate (the conclusion): https://davidsj.substack.com/p/sqlmesh-migrate
- Init with dbt: https://davidsj.substack.com/p/sqlmesh-init-t-dbt
- Init with DuckDB: https://davidsj.substack.com/p/sqlmesh-init-duckdb
- Models kind 1: https://davidsj.substack.com/p/sqlmesh-model-kinds-1
- Models kind 2: https://davidsj.substack.com/p/sqlmesh-model-kinds-2
- Plan: https://davidsj.substack.com/p/sqlmesh-plan
- Breaking and non-breaking changes: https://davidsj.substack.com/p/breaking-and-non-breaking-changes
- Test: https://davidsj.substack.com/p/sqlmesh-test
- Janitor: https://davidsj.substack.com/p/sqlmesh-janitor
-
Excerpts from the conclusion:
In short, if you use dbt-core and run it yourself in Airflow or in a container on cron etc, there is no reason not to switch to SQLMesh, and there are many reasons to do so.
The fact that it’s backwards compatible with dbt means it can’t be ignored. I know that learning a new framework is a big deal for most data folks. You are under pressure to deliver instead of trying new tools, but you can even run an existing dbt project using sqlmesh and continue to keep building dbt models if you don’t want to learn the SQLMesh way. You get the benefits of virtual data environments and the sqlmesh plan/apply workflow, which are substantial for very little lift.
Then, when you have time, you can try the SQLMesh model kinds and see that they are not so difficult or different to use.
- Title: Why SQLMesh Might be The Best dbt Alternative
- Author: Yuki Kakegawa (Yuki Kakegawa on LinkedIn, Yuki Kakegawa on Substack)
- Date: Dec. 2024
- Link to the article: https://thedatatoolbox.substack.com/p/why-sqlmesh-might-be-the-best-dbt
- Publisher: Substack
- GitHub repository: https://github.com/mattiasthalen/arcane-insight/tree/main
- Title: Arcane insight
- Overview: Arcane Insight is a data analytics project designed to harness the power of SQLMesh and DuckDB to collect, transform, and analyze data from Blizzard's Hearthstone API.
- Author: Mattias Thalén (Mattias Thalén on LinkedIn, Mattias Thalén on GitHub)
- Date: End of 2024
- Title: Multi-engine Stacks Deserve to be First Class
- Author: Julien Hurault (Julien Hurault on LinkedIn, Julien Hurault on Substack)
- Date: July 2024
- Link to the article: https://juhache.substack.com/p/multi-engine-stacks-deserve-to-be
- Publisher: Substack
- Title: The rise of the analytics pretendgineer
- Date: May 2024
- Author: Benn Stancil (Benn Stancil on LinkedIn, Benn Stancil on Substack)
- Link to the article: https://benn.substack.com/p/the-rise-of-the-analytics-pretendgineer
- Title: Unlocking data insights with Ibis and SQLMesh
- Date: May 2024
- Authors:
- Marisa Smith (Marisa Smith on LinkedIn, Marisa Smith on Tobiko blog)
- Andrew Shannon (Andrew Shannon on LinkedIn, Andrew Shannon on Tobiko blog)
- Link to the article: https://tobikodata.com/ibis-sqlmesh-unlocking-data-insights.html
- Title: SQL + DataOps = SQLMesh?
- Date: March 2024
- Author: Marti Sec (Marti Sec on blog page)
- Link to the article: https://8vi.cat/sqlmesh-quick-overview/
- Publisher: 63 orders of magnitude blog site
- Title: Is It Time To Move From dbt to SQLMesh?
- Date: Feb. 2024
- Author: Benoit Pimpaud (Benoit Pimpaud on LinkedIn)
- Link to the article: https://kestra.io/blogs/2024-02-28-dbt-or-sqlmesh
- Publisher: Kestra blog
-
Examples in this Git repository:
examples/001-simpledirectory -- Simple example, also named "quickstart".- DuckDB is used both for the executing engine and to store the state
examples/002-postgresql-statedirectory -- The same simple example as above (examples/001-simple), except that:- DuckDB is used for the executing engine and a local PostgreSQL database to store the state
examples/003-python-simple-- Example including a simple Python model- DuckDB is used both for the executing engine and to store the state
- The Python model is also executed by DuckDB
examples/004-python-ibisdirectory -- Example featuring the Python Ibis framework (translating Python dataframes from one executing engine to another, that is, like SQLGlot but for Python)- DuckDB is used both for the executing engine and to store the state
- The Python models are also executed by DuckDB
examples/005-pyspark-simpledirectory -- Simple example featuring a Python model and Spark as the executing engine- DuckDB is used to store the state
- Note that Spark cannot store the state (as per the official SQLMesh documentation about Spark)
examples/006-e2edirectory -- End-to-end full example- Still needs to be documented
examples/007-unitycatalog-simpledirectory -- Simple example featuring integration with UnityCatalog, thanks to Spark as the executing engine- DuckDB is used to store the state
- Note that Spark cannot store the state (as per the official SQLMesh documentation about Spark)
-
In most of the example directories, there is a
Makefilewith the most used commands as targets, for instance:- Clean the project from potential previous experiments:
make clean - Create the prod environment:
make plan-prod - List the tables in prod:
make list-tables-prod - Browse the content of the main table in prod:
make check-data-prod - Suggest what to do next in order to introduce a change:
make hint-change - Create a dev environment:
make plan-dev - List the tables in dev:
make list-tables-dev - Browse the content of the main table in dev:
make check-data-dev - List the differences for the main table between dev and prod:
make diff
- Clean the project from potential previous experiments:
-
A typical sequence is:
make clean
make plan-prod
make list-tables-prod
make check-data-prod
make hint-change
# Make a change on the model suggested above => vi models/some_model.{sql,py}
make plan-dev
make list-tables-dev
make check-data-dev
make diff-
This sesction is a reproduction, step by step and with the full source code, of the quickstart guide on the SQLMesh documentation
-
It features a simple example with DuckDB, both as the execution engine and to store the SQLMesh state, in a local data file (namely
db.db) ignored by Git (so that the example may be reproduced without interfering with this Git repository) -
Change to the
examples/001-simpledirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/001-simple- Clean/remove results of potential earlier tries (those files are ignored by Git):
make clean # equivalent of:
rm -rf .cache logs db.db- The
infocommand gives a high level overview of the project:
sqlmesh info
Models: 3
Macros: 0
Data warehouse connection succeeded-
The datasets are materialized as tables in a (to be created) prod environment
-
The datasets are also called models. In the remainder of the documentation, datasets, tables and models may be interchanged
-
Reference: https://sqlmesh.readthedocs.io/en/stable/quickstart/cli/#2-create-a-prod-environment
-
Launch the SQLMesh plan:
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
`prod` environment will be initialized
Models:
└── Added:
├── sqlmesh_example.full_model
├── sqlmesh_example.incremental_model
└── sqlmesh_example.seed_model
Models needing backfill (missing dates):
├── sqlmesh_example.full_model: 2024-12-24 - 2024-12-24
├── sqlmesh_example.incremental_model: 2020-01-01 - 2024-12-24
└── sqlmesh_example.seed_model: 2024-12-24 - 2024-12-24
Apply - Backfill Tables [y/n]:- Answer yes (
y) to the prompted question ("backfill tables?"):
Creating physical tables ━━━━ ... ━━━━━ 100.0% • 3/3 • 0:00:00
All model versions have been created successfully
[1/1] sqlmesh_example.seed_model evaluated in 0.03s
[1/1] sqlmesh_example.incremental_model evaluated in 0.01s
[1/1] sqlmesh_example.full_model evaluated in 0.01s
Evaluating models ━━━━━━ ... ━━━━━━━ 100.0% • 3/3 • 0:00:00
All model batches have been executed successfully
Virtually Updating 'prod' ━━━━━━━ ... ━━━━━━━━ 100.0% • 0:00:00
The target environment has been updated successfully-
It will update the DuckDB database (
db.db), which is ignored by Git -
Logs are available in the
logs/sub-directory (also ignored by Git) and cache files are to be found in the.cache/sub-directory (also ignored by Git) -
If the SQLMesh plan is run again, this time, there will be no change (the SQLMesh commands are idempotent):
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
No changes to plan: project files match the `prod` environment-
The
sqlmesh fetchdfcommand is a proxy to the backend database, DuckDB in this example. The content of the backend database may therefore be queries either through thesqlmesh fetchdfcommand or directly with the backend database. -
In the remaining of this sub-section, DuckDB is used directly. All the SQL queries could also be executed thanks to the
sqlmesh fetchdfcommand, for instance:
sqlmesh fetchdf "select * from sqlmesh_example.incremental_model"
id item_id event_date
0 1 2 2020-01-01
...
6 7 1 2020-01-07- Check the models with DuckDB
- Launch DuckDB on the just created/updated database (namely
db.db) (as a reminder, to quit the Duck shell, either type Control-D or the.quitcommand):
- Launch DuckDB on the just created/updated database (namely
duckdb db.db- List all the tables:
D show all tables;- List the items of the
seed_modeltable:
D select * from sqlmesh_example.seed_model;
┌───────┬─────────┬────────────┐
│ id │ item_id │ event_date │
│ int32 │ int32 │ date │
├───────┼─────────┼────────────┤
│ 1 │ 2 │ 2020-01-01 │
...
│ 7 │ 1 │ 2020-01-07 │
└───────┴─────────┴────────────┘- List the items of the
incremental_modeltable:
D select * from sqlmesh_example.incremental_model;
┌───────┬─────────┬────────────┐
│ id │ item_id │ event_date │
│ int32 │ int32 │ date │
├───────┼─────────┼────────────┤
│ 1 │ 2 │ 2020-01-01 │
...
│ 7 │ 1 │ 2020-01-07 │
└───────┴─────────┴────────────┘- List the items of the
full_modeltable:
D select * from sqlmesh_example.full_model;
┌─────────┬────────────┐
│ item_id │ num_orders │
│ int32 │ int64 │
├─────────┼────────────┤
│ 2 │ 1 │
│ 1 │ 5 │
│ 3 │ 1 │
└─────────┴────────────┘- Quit DuckDB:
D .quit- Launch the tests:
sqlmesh test
.
----------------------------------------------------------------------
Ran 1 test in 0.021s
OK-
Reference: https://sqlmesh.readthedocs.io/en/stable/quickstart/cli/#4-work-with-a-development-environment
-
Launch the plan command, specifying the dev environment (which does not exist yet):
sqlmesh plan dev
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
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
...
Directly Modified: sqlmesh_example__dev.incremental_model (Non-breaking)
└── Indirectly Modified Children:
└── sqlmesh_example__dev.full_model (Indirect Non-breaking)
Models needing backfill (missing dates):
└── sqlmesh_example__dev.incremental_model: 2020-01-01 - 2024-12-24
Enter the backfill start date (eg. '1 year', '2020-01-01') or blank to backfill from the beginning of history:
Enter the backfill end date (eg. '1 month ago', '2020-01-01') or blank to backfill up until '2024-12-25 00:00:00':
Apply - Backfill Tables [y/n]:- Answer yes when prompted about applying the change:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━━ ... ━━━━━ 100.0% • 3/3 • 0:00:00
All model versions have been created successfully
[1/1] sqlmesh_example__dev.incremental_model evaluated in 0.04s
Evaluating models ━━━━━ ... ━━━━━━ 100.0% • 1/1 • 0:00:00
All model batches have been executed successfully
Virtually Updating 'dev' ━━━━━ ... ━━━━━ 100.0% • 0:00:00
The target environment has been updated successfully- Check the content of the updated table in the dev environment:
sqlmesh fetchdf "select * from sqlmesh_example__dev.incremental_model"
id item_id new_column event_date
0 1 2 z 2020-01-01
...
6 7 1 z 2020-01-07- Even though there is a new
full_modeltable in the dev environment, its content is the same as the same table on the prod environment (as this table does not use the new column yet):
sqlmesh fetchdf "select * from sqlmesh_example__dev.full_model"
item_id num_orders
0 2 1
1 1 5
2 3 1- There is a command, namely
table_diff, to display the differences between two environment for a given table:
sqlmesh table_diff prod:dev sqlmesh_example.incremental_model
Schema Diff Between 'PROD' and 'DEV' environments for model 'sqlmesh_example.incremental_model':
└── Added Columns:
└── new_column (TEXT)
Row Counts:
└── FULL MATCH: 7 rows (100.0%)
COMMON ROWS column comparison stats:
pct_match
item_id 100.0-
Reference: https://sqlmesh.readthedocs.io/en/stable/quickstart/cli/#51-apply-updates-to-prod
-
Launch the plan command, and when not specifying any environment, the prod environment is assumed:
sqlmesh plan # prod
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
Differences from the `prod` environment:
Models:
├── Directly Modified:
│ └── sqlmesh_example.incremental_model
└── Indirectly Modified:
└── sqlmesh_example.full_model
...
Directly Modified: sqlmesh_example.incremental_model (Non-breaking)
└── Indirectly Modified Children:
└── sqlmesh_example.full_model (Indirect Non-breaking)
Apply - Virtual Update [y/n]:- Answer yes when prompted about applying the change:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━ ... ━━━━━ 100.0% • 3/3 • 0:00:00
All model versions have been created successfully
Virtually Updating 'prod' ━━━━ ... ━━━━━ 100.0% • 0:00:00
The target environment has been updated successfully
Virtual Update executed successfully-
Reference: https://sqlmesh.readthedocs.io/en/stable/quickstart/cli/#5.2-validate-updates-in-prod
-
Check that the prod table has been updated with the new column:
sqlmesh fetchdf "select * from sqlmesh_example.incremental_model"
id item_id new_column event_date
0 1 2 z 2020-01-01
...
6 7 1 z 2020-01-07- The
table_diffcommand now reports that the tables are the same in both the dev and prod environments:
sqlmesh table_diff prod:dev sqlmesh_example.incremental_model
Schema Diff Between 'PROD' and 'DEV' environments for model 'sqlmesh_example.incremental_model':
└── Schemas match
Row Counts:
└── FULL MATCH: 7 rows (100.0%)
COMMON ROWS column comparison stats:
pct_match
item_id 100.0
new_column 100.0- For convenience, all the clean commands are featured in a single Makefile target:
make clean- For reference, the following cleaning operations are performed
- As DuckDB stores both the state and the datasets, cleaning up is as
straightforward as deleting the DuckDB data file, namely
db.db:
rm -f db.db- Delete also the log and the cache directories:
make clean # equivalent of:
rm -rf .cache logs- Comment the clause for the
zcolumn in theincremental_modelmodel:
make hint-change
grep "z" models/incremental_model.sql
--'z' AS new_column, -- Added column- The project is now ready to start afresh, with no memory nor any change when compared to the Git repository
-
DuckDB is still the execution engine
-
The state is stored within a local PostgreSQL database. See the Local PostgreSQL server section on how to install and setup such a local PostgreSQL database
-
Change to the
examples/002-postgresql-statedirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/002-postgresql-state- Clean/remove results of potential earlier tries (those files are ignored by Git):
make clean # equivalent of:
rm -rf .cache logs db.db db.db.wal- The other steps (i.e., SQLMesh plan, introduce a change, SQLMesh dev environment, check the updates, SQLMesh plan to merge the updates on prod, cleanup the project) are the same as in the quickstart example. The Makefile has been adapted to take PostgreSQL into account
-
References:
-
Change to the
examples/003-python-simpledirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/003-python-simple-
To create a project skeleton with Python models, simply use the
sqlmesh initcommand, that is, using the default dialect (being DuckDB), like for SQL models. So, there are no difference, at that stage, between a project for SQL models and a project for Python models -
Note that the
sqlmesh initcommand has already been performed and the resulting project skeleton is part of this Git repository -
Note that the
sqlmesh initcommand acceptspythonas a dialect.- But if a project skeleton is created that way (i.e., with the
sqlmesh init pythoncommand), the resulting project skeleton looks similar to a regular SQL-model project, with the important difference that the dialect in theconfig.yamlconfiguration file will bepythonrather thanduckdb. - And then, when launching the SQLMesh plan (with the
sqlmesh plancommand), the underlying SQLGlot engine will fail with some cryptic error:
- But if a project skeleton is created that way (i.e., with the
make plan-prod # equivalent of:
sqlmesh plan
Error: Required keyword: 'this' missing for <class 'sqlglot.expressions.Between'>. Line 1, Col: 239.
odel WHERE BETWEEN(scope[None][event_date], DATESTRTODATE('1970-01-01'), DATESTRTODATE('1970-01-01'))- Launch the SQLMesh plan:
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
`prod` environment will be initialized
Requirements:
+ pandas==2.2.3
Models:
└── Added:
├── sqlmesh_example.full_model
├── sqlmesh_example.incremental_model
├── sqlmesh_example.seed_model
└── sqlmesh_example.full_model_python
Models needing backfill (missing dates):
├── sqlmesh_example.full_model: 2024-12-26 - 2024-12-26
├── sqlmesh_example.incremental_model: 2020-01-01 - 2024-12-26
├── sqlmesh_example.seed_model: 2024-12-26 - 2024-12-26
└── sqlmesh_example.full_model_python: 2024-12-26 - 2024-12-26- Accept the suggestions at the prompt:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━ ... ━━━━ 100.0% • 4/4 • 0:00:00
All model versions have been created successfully
[1/1] sqlmesh_example.seed_model evaluated in 0.03s
[1/1] sqlmesh_example.full_model_python evaluated in 0.01s
[1/1] sqlmesh_example.incremental_model evaluated in 0.01s
[1/1] sqlmesh_example.full_model evaluated in 0.01s
Evaluating models ━━━━ ... ━━━━ 100.0% • 4/4 • 0:00:00
All model batches have been executed successfully
Virtually Updating 'prod' ━━━━━ ... ━━━━━ 100.0% • 0:00:00
The target environment has been updated successfully
- Use the
fetchdfcommand:- To list the tables:
make list-tables-prod # equivalent of
sqlmesh fetchdf "use sqlmesh_example; show tables"
name
0 full_model
1 full_model_python
2 incremental_model
3 seed_model- To browse the content of the
Python model
table (that is, the
sqlmesh_example.full_model_pythontable):
make check-data-prod # equivalent of:
sqlmesh fetchdf "use sqlmesh_example; select * from full_model_python"
id name
0 1 Laura
1 2 John
2 3 Lucie- Launch the
auditcommand:
sqlmesh audit # equivalent of
Found 2 audit(s).
assert_positive_order_ids on model sqlmesh_example.full_model ✅ PASS.
not_null on model sqlmesh_example.full_model_python ✅ PASS.
Finished with 0 audit errors and 0 audits skipped.
Done.- Launch the
testcommand:
sqlmesh test # equivalent of
sqlmesh test
.
----------------------------------------------------------------------
Ran 1 test in 0.016s
OK- Clean/remove results of potential earlier tries (those files are ignored by Git):
make clean # equivalent of:
rm -rf .cache logs db.db- Comment the clause for the
zcolumn in theincremental_modelmodel:
make hint-change # equivalent of:
grep "z" models/incremental_model.sql
--'z' AS new_column, -- Added column-
References:
- Article on Tobiko blog site: https://tobikodata.com/ibis-sqlmesh-unlocking-data-insights.html
- Python models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/
- PySpark models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/#pyspark
- Ibis SQLMesh project: https://github.com/TobikoData/sqlmesh-examples/tree/main/002_ibis
- The Ibis framework: https://github.com/ibis-project/ibis
-
Change to the
examples/004-python-ibisdirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/004-python-ibis-
This example has not been generated with the
sqlmesh initcommand, but it has rather been fully imported from the SQLMesh example Git repository (withgit clone git@github.com:TobikoData/sqlmesh-examples.gitinto a temporary directory, and thenrsync -avfrom that temporary directory unto this currentpython-ibisdirectory) -
It features the Ibis framework, a Python library to translate dataframes from one dialect to another (it is similar to SQLGlot, but for Python instead of for SQL)
- Launch the SQLMesh plan:
make plan-prod # equivalent of
sqlmesh plan
New environment `prod` will be created from `prod`
Summary of differences against `prod`:
Models:
└── Added:
├── ibis.full_model
├── ibis.ibis_full_model_python
├── ibis.ibis_full_model_sql
├── ibis.incremental_model
└── ibis.seed_model
Models needing backfill (missing dates):
├── ibis.full_model: 2020-01-01 - 2024-12-26
├── ibis.ibis_full_model_python: 2020-01-01 - 2024-12-26
├── ibis.ibis_full_model_sql: 2020-01-01 - 2024-12-26
├── ibis.incremental_model: 2020-01-01 - 2024-12-26
└── ibis.seed_model: 2024-12-26 - 2024-12-26- Answer yes to the prompt asking whether to apply and backfill the models:
Apply - Backfill Tables [y/n]: y
Creating physical table ━━━━ ... ━━━━ 100.0% • 5/5 • 0:00:00
All model versions have been created successfully
[1/1] ibis.seed_model evaluated in 0.00s
[1/1] ibis.incremental_model evaluated in 0.01s
[1/1] ibis.full_model evaluated in 0.01s
[1/1] ibis.ibis_full_model_python evaluated in 0.06s
[1/1] ibis.ibis_full_model_sql evaluated in 0.03s
Evaluating models ━━━ ... ━━━━ 100.0% • 5/5 • 0:00:00
All model batches have been executed successfully
Virtually Updating 'prod' ━━━ ... ━━━━ 100.0% • 0:00:00
The target environment has been updated successfully- Note that the result of the
sqlmesh fetchdf "show all tables"command may be truncated (i.e., the names of the tables do not appear). It is therefore advised to use a specific schema (e.g.,ibishere):
make list-tables-prod # equivalent of
sqlmesh fetchdf "use ibis; show tables" name
0 full_model
1 ibis_full_model_python
2 ibis_full_model_sql
3 incremental_model
4 seed_model
- Browse the content of the incremental model/table:
make check-data-prod # equivalent of
sqlmesh fetchdf "select * from ibis.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
-
Anyway, DuckDB may also be used to explore the tables and the content. In the remainder of this sub-section, DuckDB will be used to explore the data
-
Launch the DuckDB shell
- Note that, as specified within the
config.yamlconfiguration file, the DuckDB data file isdata/local.duckdb - As may be seen in the
various models,
the schema is
ibis - In order to quit the DuckDB shell, type Control-D or the
.quitcommand
- Note that, as specified within the
duckdb data/local.duckdbD use ibis;
D show tables;
┌────────────────────────┐
│ name │
│ varchar │
├────────────────────────┤
│ full_model │
│ ibis_full_model_python │
│ ibis_full_model_sql │
│ incremental_model │
│ seed_model │
└────────────────────────┘- Leave the DuckDB shell:
D .quit- Run the project (as there have been no change yet, running the project does not do anything):
sqlmesh run
Run finished for environment 'prod'- Launch the tests:
sqlmesh test
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK- Launch the audit:
sqlmesh audit
Found 3 audit(s).
assert_positive_order_ids on model ibis.full_model ✅ PASS.
assert_positive_order_ids on model ibis.ibis_full_model_python ✅ PASS.
assert_positive_order_ids on model ibis.ibis_full_model_sql ✅ PASS.
Finished with 0 audit errors and 0 audits skipped.
Done.-
References:
-
Change to the
examples/005-pyspark-simpledirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/005-pyspark-simple- The project has been initialized with the
sqlmesh init sparkcommand
- Launch the SQLMesh plan:
make plan-prod # equivalent of:
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
`prod` environment will be initialized
Requirements:
+ pyspark==3.5.4
Models:
└── Added:
├── docs_example.pyspark
├── sqlmesh_example.full_model
├── sqlmesh_example.incremental_model
└── sqlmesh_example.seed_model
Models needing backfill (missing dates):
└── docs_example.pyspark: 2024-12-26 - 2024-12-26- Answer yes to the prompt:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━ ... ━━━━━ 100.0% • 4/4 • 0:00:00
All model versions have been created successfully
-
Example still to be created and documented
-
Reference: https://sqlmesh.readthedocs.io/en/stable/examples/incremental_time_full_walkthrough/
-
Change to the
examples/006-e2edirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/006-e2e-
References:
- DataBricks engine: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/databricks/
- Spark engine: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/spark/
- Python models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/
- PySpark models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/#pyspark
-
Change to the
examples/007-databricks-simpledirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/007-databricks-simple- The project has been initialized with the
sqlmesh init sparkcommand
-
First, specify the following environment variables (for instance, in the
~/.bashrcor~/.zshrcShell configuration file):DBS_SVR_HST- DataBricks host, e.g.,<some-workspace>.cloud.databricks.comDBS_HTTP_PATH- DataBricks HTTP path, e.g.,sql/protocolv1/o/<wksp-id>/<cluster-id>DBS_PAT- DataBricks Personal Access Token (PAT)DBS_SCH- DataBricks schema/database, on which the DataBricks cluster should have the right to write. That schema is the one used by the SQLMesh models
-
Create the
.envenvironment file, by substituting the environment variables in the.env.samplefile:
envsubst < .env.sample > .env-
That
.envfile is handled in a different way from all the.infiles, as it is imported by theMakefile. ThatMakefilecan therefore not alter the.envfile itself, otherwise there will be a catch 22 situation -
Execute the
init-filestarget in order to substitute the environment variables into the model files, the test files and the configuration file:
make init-files- Launch the SQLMesh plan:
make plan-prod # equivalent of:
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
`prod` environment will be initialized
Requirements:
+ pyspark==3.5.4
Models:
└── Added:
├── docs_example.pyspark
├── sqlmesh_example.full_model
├── sqlmesh_example.incremental_model
└── sqlmesh_example.seed_model
Models needing backfill (missing dates):
└── docs_example.pyspark: 2024-12-26 - 2024-12-26- Answer yes to the prompt:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━ ... ━━━━━ 100.0% • 4/4 • 0:00:00
All model versions have been created successfully
-
References:
- Data Engineering Helpers - Knowledge Sharing - Unity Catalog (UC)
- DataBricks engine: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/databricks/
- Spark engine: https://sqlmesh.readthedocs.io/en/stable/integrations/engines/spark/
- Python models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/
- PySpark models: https://sqlmesh.readthedocs.io/en/stable/concepts/models/python_models/#pyspark
- Unity Catalog docs
-
Change to the
examples/008-unitycatalog-simpledirectory within the SQLMesh dedicated directory:
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh/examples/008-unitycatalog-simple- The project has been initialized with the
sqlmesh init sparkcommand
-
See Data Engineering Helpers - Knowledge Sharing - Spark on how to install and use the Spark Connect server and client
-
(If not already done so,) Launch the Spark Connect server:
sparkconnectstart- (At the end of the work session,) Shutdown the Spark Connect server:
sparkconnectstop- Launch the SQLMesh plan:
make plan-prod # equivalent of:
sqlmesh plan
======================================================================
Successfully Ran 1 tests against duckdb
----------------------------------------------------------------------
`prod` environment will be initialized
Requirements:
+ pyspark==3.5.4
Models:
└── Added:
├── docs_example.pyspark
├── sqlmesh_example.full_model
├── sqlmesh_example.incremental_model
└── sqlmesh_example.seed_model
Models needing backfill (missing dates):
└── docs_example.pyspark: 2024-12-26 - 2024-12-26- Answer yes to the prompt:
Apply - Backfill Tables [y/n]: y
Creating physical tables ━━━━ ... ━━━━━ 100.0% • 4/4 • 0:00:00
All model versions have been created successfully
- Clone this Git repository and move into its SQLMesh directory:
mkdir -p ~/dev/knowledge-sharing
git clone https://github.com/data-engineering-helpers/ks-cheat-sheets ~/dev/knowledge-sharing/ks-cheat-sheets
cd ~/dev/knowledge-sharing/ks-cheat-sheets/data-processing/sqlmesh-
See also Data Engineering Helpers - Knowledge Sharing - DuckDB for more details on how to install DuckDB
-
DuckDB may be installed through the native packaging utility, when available (for instance, on MacOS,
brew install duckdb), through binary artifacts (on Linux) or through one of the programming stack utilities (for instance, for the Python stack,pip install -U duckdb)
-
This sub-section is just for reference. It is not used for most of the examples explored in the Quickstart section
-
See also GitHub - Data Engineering Helpers - KS - DuckDB - Public catalogs
-
Launch the DuckDB shell:
$ duckdb- (In DuckDB,) attach to a public catalog, for instance the BlueSky catalog:
D attach 'https://hive.buz.dev/bluesky/catalog' as bluesky;- Check that the BlueSky data is available:
D select count(*)/1e6 as nb_rows from bluesky.jetstream;
┌─────────┐
│ nb_rows │
│ double │
├─────────┤
│ 1.0 │
└─────────┘
D select * from bluesky.jetstream limit 10;- To leave the DuckDB shell, either type Control-D or the
.quitcommand:
D .quit-
See Data Engineering Helpers - Knowledge Sharing - Unity Catalog (UC) (in this same Git repository) for details on how to install and use Unity Catalog (UC)
-
For just some trial of Unity Catalog, it is generally easier to use Docker compose (
docker compose up) -
For the seasoned data engineer, it makes however more sense to know what is under the hood and to maintain Unity Catalog (UC) natively with a Java Virtual Machine (JVM) and with a local PostgreSQL database to store the catalog (it can be the same PostgreSQL service storing some SQLMesh states, but with different databse, schema and user).
- Building the UC JARs and publishing them goes something like:
cd ~/dev/infra/unitycatalog
git pull
sbt package publishLocal- In a dedicated tab of the terminal window, launch the Unity Catalog
(Control-C to terminate the service)
- With the default port (
8080):
- With the default port (
./bin/start-uc-server- With an alternative port (e.g.,
9090):
./bin/start-uc-server -p 9090- (Optionally,) To start the UC UI, in another dedicated tab of the terminal window:
- Start the UI through Yarn (Control-C to terminate the service):
cd ui
yarn install
yarn start- To interact with the UC
- When the UC server has been started on the default port (entities:
schema,volume,model_version,metastore,auth,catalog,function,permission,registered_model,user,table):
- When the UC server has been started on the default port (entities:
bin/uc <entity> <operation>- When the UC server has been started on an alternative port (say
9090), specify the--serverparameter before the entity:
bin/uc --server http://localhost:9090 <entity> <operation>- List the catalogs (the default one is usually called
unity):
bin/uc catalog list- List the schemas (the default one is usually called
default):
bin/uc schema list --catalog unity- List the tables:
bin/uc table list --catalog unity --schema default- Browse the records of a given table (
numbersis a sample usually provided with UC at the installation):
bin/uc table read --full_name unity.default.numbers-
See Data Engineering Helpers - Knowledge Sharing - Spark on how to install and use the Spark Connect server and client
-
See also the relevant documentation when using Spark with Unity Catalog (UC): https://docs.unitycatalog.io/integrations/unity-catalog-spark/
-
For consistency reason, it is better, for the Unity Catalog (UC) connector, to use the JAR package generated by SBT (and published locally in the local Ivy2/Maven cache)
- Check that the Unity Catalog Spark connector JAR package is in the local Ivy2/Maven cache:
ls -lFh ~/.ivy2/jars/io.unitycatalog*- (If not already done so,) Launch the Spark Connect server:
sparkconnectstart- (At the end of the work session,) Shutdown the Spark Connect server:
sparkconnectstop- SQLMesh comes as a Python package, and may therefore installed simply with the Python packager. For instance:
python -mpip install -U "sqlmesh[web,databricks]"-
The SQLMesh package installs two executable scripts, namely
sqlmeshandsqlmesh_cicd, which are usually stored along side the other Python packages. For instance, with PyEnv, it will end up as wrappers in~/.pyenv/shims/. -
Usually, for the Shell (e.g., Bash or Zsh) to become aware of those newly installed executables scripts, it has to be refreshed (with the
execcommand)- For the Bash Shell:
exec bash- For the Zsh Shell:
exec zsh- Check the version of the just installed SQLMesh package:
sqlmesh --version
0.141.1-
Note that most of the projetcs, to be found in Git repositories, have already been initialized; they no longer need initializing.
-
Clean/remove results of potential earlier tries (those files are ignored by Git):
make clean # equivalent of:
rm -rf .cache logs db.db- To create a new project from scratch, execute the
sqlmesh initcommand, specifying which SQL dialect to use (among, for instance, DataBricks, Drill, DuckDB, Hive, MySQL, PostgreSQL, Presto, Redshift, Snowflake, Spark, SQLite, Tableau, Trino).- Note that
pythonis also available as a dialect:sqlmesh init pythonwill create a project skeleton populated for Python as a dialect - The simple example, described in the
SQLMesh getting started page,
has been created in the
simple-exampledirectory of this Git repository, with thesqlmesh init duckdbcommand. The resulting file structure has been added and committed to the Git repository: - Configuration:
config.yamlfile - Models:
models/directory - Seed files:
seeds/directory- Which contain a seed/example data set, namely
seed_data.csv
- Which contain a seed/example data set, namely
- Shared audit files:
audits/directory - Unit test files:
tests/directory - Macro files:
macros/directory
- Note that
- In a separate terminal tab, as the default local port (
8000) may already be taken by other processes (e.g., LakeFS is running on the8000port by default), launch the SQLMesh UI by specifying a port not already in use:
sqlmesh ui --port 9090
INFO: Started server process [7586]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:9090 (Press CTRL+C to quit)- With a web browser, open http://localhost:9090
-
See also:
-
When SQLMesh is used with the built-in scheduler
- DuckDB is used by default to store the state permanently (by default,
in the
db.dbDuckDB database file) - Another database may be used to store the state, for instance PostgreSQL
- DuckDB is used by default to store the state permanently (by default,
in the
-
When SQLMesh is used with external schedulers (for instance, Airflow), the state is stored on the backend database of the external scheduler (in the case of Airflow, that is often a PostgreSQL database)
-
In the Data Engineering Helpers - Knowledge Sharing - PostgreSQL guide, there is a section detailing how to create a
sqlmeshdatabase, asqlmeshschema and thesqlmeshuser on a local PostgreSQL database
-
See https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#overrides on the relevant SQLMesh configuration options
- In particular, the password of the PostgreSQL database user may be specified
within an environment variable, for instance
SQLMESH__GATEWAYS__LOCAL__STATE_CONNECTION__PASSWORD - However, within that documentation, another method (not to expose the password in the Git repository) has been chosen; see the next bullet point for that alternative method
- In particular, the password of the PostgreSQL database user may be specified
within an environment variable, for instance
-
As the
config.yamlconfiguration files contain credentials for the PostgreSQL database (even though those credentials are just some samples for local services, some security scanners may bump onto them in GitHub repositories and report them as false positives)- Git stores only a sample version of the
config.yamlconfiguration file, namelyconfig.yaml.sample - The
config.yamlconfiguration file, that SQLMesh is expecting, has to be copied from theconfig.yaml.samplefile and:- The password has to be adjusted in it
- That
config.yamlconfiguration file is ignored by Git (so that the credentials do not appear in clear in the Git repository) - The sequence is therefore as the following:
- Git stores only a sample version of the
cp config.yaml.sample config.yaml
sed -i.bak -e 's/<sqlmesh-pass>/<REPLACE-HERE-BY-THE-POSTGRESQL-USER-PASSWORD>/' config.yaml && rm -f config.yaml.bak- Check that the content of the
config.yamlconfiguration file seems correct
cat config.yaml | yq -r '.gateways.local.state_connection'type: postgres
host: localhost
port: 5432
database: sqlmesh
user: sqlmesh
password: <REPLACE-HERE-BY-THE-POSTGRESQL-USER-PASSWORD>- SQLMesh is now ready to run with:
- DuckDB as the execution engine
- Local PostgreSQL database server to store the state
- See Local PostgreSQL to store the state for the walkthrough
- The next sub-sections detail a few specifities of having PostgreSQL to store the SQLMesh state
-
Follow the same steps as in the pure DuckDB example
- The behaviour of SQLMesh should be exactly the same, except that the state is now stored in the local PostgreSQL database, as can be checked with the state-related tables in the PostgreSQL database. See the remainder of this sub-section for the details
-
After
sqlmesh plan- The datasets are in the DuckDB database, namely the
dbwost.dbfile. The content may still be queried with thesqlmesh fetchdfcommand, e.g.:
- The datasets are in the DuckDB database, namely the
sqlmesh fetchdf "select * from sqlmesh_example.full_model"- The state is stored in the PostgreSQL database. For instance:
- List the state-related tables (the
-toption is to display tuples only, that is, turn off the header, footer and comments):
- List the state-related tables (the
# psql -h localhost -U sqlmesh -d sqlmesh -t -c "\dt"
psql -h localhost -U sqlmesh -d sqlmesh -t -c "select table_name from information_schema.tables where table_schema = 'sqlmesh'" _snapshots
_environments
_auto_restatements
_intervals
_plan_dags
_versions* List the (virtual data) environments:
psql -h localhost -U sqlmesh -d sqlmesh -t -c "select * from _environments;"* List the intervals:
psql -h localhost -U sqlmesh -d sqlmesh -t -c "select * from _intervals;" 36c6a51271164c7687215c2d6927c252 | 1735489690863 | "db"."sqlmesh_example"."seed_model" | 372700188 | 2185867172 | 1734998400000 | 1735430400000 | f | f | f | f
1aae0371a4664f51925bd0101984be12 | 1735489690871 | "db"."sqlmesh_example"."incremental_model" | 1463271556 | 1880815781 | 1577836800000 | 1735430400000 | f | f | f | f
15be8abdaf62493c8ebfab91f72a9099 | 1735489690881 | "db"."sqlmesh_example"."full_model" | 3906121019 | 2278521865 | 1734998400000 | 1735430400000 | f | f | f | f- Delete the datasets in DuckDB, the logs and the cache directories:
make clean # equivalent of:
rm -rf db.db .cache logs-
In order to clean the SQLMesh state in the local PostgreSQL database, there is a Bash script, namely
tools/clean-pg-state.shin this Git repository- That Bash script features a few sanity checks (for instance, that the
psqlcommand exists and works) - If the PostgreSQL parameters are different from the default ones,
they can be altered in
that
tools/clean-pg-state.shBash script at its top - The remainded of this sub-section still details on how to clean the SQLMesh state from the local PostgreSQL database manually
- That Bash script features a few sanity checks (for instance, that the
-
Specify the list of the state-related tables in a Shell array variable:
table_list=($(psql -h localhost -U sqlmesh -d sqlmesh -t -c "select table_name from information_schema.tables where table_schema = 'sqlmesh'"))- Clean/drop the state-related tables in PostgreSQL:
for table in "${table_list[@]}"; do echo "Dropping ${table} table..."; psql -h localhost -U sqlmesh -d sqlmesh -c "drop table if exists ${table};"; echo "... ${table} table dropped"; done- If needed, comment the clause for the
zcolumn in theincremental_modelmodel (e.g., in theexamples/002-postgresql-state/models/incremental_model.sqlmodel file)- It should read something like:
grep "z" models/incremental_model.sql
--'z' AS new_column, -- Added column- The project is now ready to start afresh, with no memory nor any change when compared to the Git repository