Overview
A dbt project is folders of SQL plus YAML: models/, tests/, sources.yml, dbt_project.yml. Staging models clean sources. Marts answer business questions.
On this page7 sections
The picture
Config at the root. Models in folders. Tests and sources in YAML. Staging feeds marts.
A dbt project is a folder of files, not a GUI. SQL files are models. YAML files name sources and tests. One config file, dbt_project.yml, tells dbt the project name and where models live. Once you can read that tree, every dbt repo looks familiar.
models/ holds SELECT statements, one file per model. tests/ or a schema.yml file holds tests (not_null, unique, accepted values). sources.yml (or a sources: block in YAML) names raw tables you do not own. Macros, seeds, and snapshots appear later. Start with models, tests, sources, and the project config.
This tab has no project tree. A SELECT from orders stands in for a model file. You still write compiled SQL. Nothing here runs `dbt run` or reads dbt_project.yml.
Why this shape
Without a shared layout, one teammate puts cleanup SQL next to dashboard SQL in a single file. Another teammate copies the same FROM orders into five marts. A rename becomes a scavenger hunt. Folders are not decoration. They encode the rule: clean once in staging, answer questions in marts.
YAML sources give raw tables a name the rest of the project can point at. When the loader lands data in a new schema, you change the source definition instead of editing twenty models. Tests next to models mean a bad key fails the build before a dashboard ships the wrong number.
Walk the boxes
Treat the project like a kitchen: prep on one counter (staging), finished plates on another (marts), recipes in a binder (YAML), and a label on the door (dbt_project.yml).
| Path | What it is | What you put there |
|---|---|---|
| dbt_project.yml | Project config | Name, model paths, materialization defaults |
| models/ | SQL models | One .sql file per SELECT |
| models/staging/ | Staging folder | Rename, cast, light filter. No joins. |
| models/marts/ | Marts folder | Facts and dimensions analysts query |
| sources.yml | Raw table registry | Schema and table names you do not own |
| tests/ or schema.yml | Contracts | unique, not_null, relationships |
A staging model is a SELECT from one source. A mart is a SELECT from staging models. In a real project those files sit on disk. In this tab, the same SQL is a query in the editor. Here is a stand-in for models/staging/stg_orders.sql, already compiled:
A matching example
Run the example below in this tab. Read the input, follow the code, then check the output matches what you expect.
-- Stand-in for models/staging/stg_orders.sql (compiled, no Jinja)
SELECT
order_id,
order_status,
order_total
FROM orders
LIMIT 10;A mart would read from that staging model, not from the raw table. After compile, that looks like FROM stg_orders. You have not built stg_orders as a view in this tab, so the exercise keeps FROM orders as the stand-in. The folder rule still holds: staging first, marts second.
-- Stand-in for models/marts/fct_orders.sql (compiled)
-- Production would read FROM stg_orders after ref() compiles.
-- Here the raw table stands in so the SELECT can run.
SELECT
order_id,
order_status,
order_total
FROM orders
WHERE order_status = 'paid'
LIMIT 10;- Create or clone a project so dbt_project.yml exists at the root.
- Declare sources in YAML: the raw schema and table names.
- Add one staging model per source table under models/staging/.
- Add marts under models/marts/ that read staging, not raw.
- Add tests. Run the CLI on your laptop when you are ready. This tab only runs the SELECT.
Common beginner questions
Is dbt_project.yml optional?
No. dbt needs it to know the project name and where to find models. You will not edit it in this tab. On your laptop it is the first file you look at in a new repo.
Can I put all models in one folder?
You can. Teams still split staging and marts because the jobs differ. Mixing them makes it easy to join in staging or to query raw tables from a mart.
Where do tests live?
Many projects put tests in schema.yml next to the models they protect. Some put singular test SQL files under tests/. Both are valid. The idea is the same: a query that should return zero bad rows.
Do not join in staging
stg_orders should not join payments. If you need both tables, that is a mart. Staging stays one source, one file, one grain.
The editor is one model file
Imagine the exercise SQL saved as models/staging/stg_orders.sql. dbt would wrap it in a view or table. You still write only the SELECT.
What comes next
Those SQL files often contain Jinja: {{ ref('stg_orders') }} instead of a hardcoded table name. The next lesson shows what Jinja is, what compile does, and why this tab still wants plain SQL.
Practice
Run Sample to see a model-shaped SELECT. Then complete the exercise: select order_id, order_status, and order_total from orders, limited to 10 rows. Treat it as the body of a .sql model file.
Practicals · load into the editor
After you read the theory, run these in the pane on the right. They execute in this tab, no cluster.