Module 8: Project Kioskos Dbt Warehouse

Running `dbt build` end to end

Description

This is the moment the whole guide was building toward: a single command, dbt build, that rebuilds Kiosko's complete project — twelve models, one snapshot, thirty-three data_tests — in the correct order, resolved automatically by the ref() graph you kept declaring module by module, with nobody needing to remember what sequence to run anything in. This lesson runs that command, reads its complete output line by line, and explains an operational piece that only shows up the first time someone starts from a freshly cloned project: why the snapshot still needs two runs to keep P002's complete history.

Connection to the module. Lessons 3 and 4 prepared each piece separately and confirmed the complete structure with nothing run end to end. This lesson is the whole module's first time the project runs as a whole — the same moment you already lived, at a smaller scale, in modules 5, 6, and 7's mini-projects, now over double the models.

An analogy: the full dress rehearsal of the complete show

Every module in this guide closed with its own mini-project — a partial rehearsal, of one section of the show. This lesson is the full dress rehearsal of the entire show, start to finish: sources, the snapshot, the twelve models, the thirty-three tests, in the exact order they're going to run on opening night. A dress rehearsal doesn't repeat any scene separately — that already happened, scene by scene, in each module's partial rehearsals — it confirms the transitions between scenes work, that the complete cast can carry the show with nobody having to remind anyone what their next line is.

Before running: the special case of a freshly cloned project

If you've been following this guide module by module, over the same kiosko.duckdb that's existed since module 1, you can skip straight to the next section — your snapshot already has dim_product_snapshot's five historical rows (P002 with its two versions), and dbt build is going to keep them with no extra step.

If instead you're starting from a freshly cloned kiosko_analytics/, with no kiosko.duckdb yet, module 5 already warned you about this, and module 7's mini-project Exercise 2 confirmed it with a list of steps: a dbt build run once, from scratch, executes dbt snapshot once, against products's current state (products_v2.csv, the one _sources.yml declares today) — with no earlier frame to compare against. The result is a four-row snapshot, all open, with none of P002's history modules 5's lessons 5 and 6 built with two separate runs.

To reproduce the complete history before running dbt build over the whole project, repeat module 5's exact sequence:

# 1. point products at the original catalog
# (edit _sources.yml: external_location: "raw_data/kiosko/products_v1.csv")
dbt snapshot

# 2. point products back at the current catalog
# (edit _sources.yml: external_location: "raw_data/kiosko/products_v2.csv")
dbt snapshot

What to expect. Two runs of PASS=1 each, and a dim_product_snapshot table with five rows — P002 with two versions, dbt_valid_from/dbt_valid_to marking exactly August 15, 2026 as the cutoff — the same table you already know from module 5. This is a step you do once, never again on later runs: once the history exists, dbt build keeps it automatically, with no additional change to _sources.yml.

The complete command

With the snapshot in its correct state (five rows, P002 historized), run the whole project:

dbt build --vars '{"run_date": "2026-08-09"}'

Remember why --vars is still required, the same reason module 6 already explained: fact_orders is an incremental model, and as soon as the table already exists (which is true since earlier modules' first dbt run), is_incremental() is True, and fact_orders.sql's {% if is_incremental() %} block needs var("run_date") to compile. Without --vars, this command fails with the same Required var 'run_date' not found you already saw in module 6 and in module 7's lesson 7.

What to expect.

Running with dbt=1.12.2
Registered adapter: duckdb=1.11.0
Found 12 models, 33 data tests, 1 snapshot, 4 sources, 502 macros

Concurrency: 4 threads (target='dev')

1 of 46 START sql table model main.dim_date ..................................... [RUN]
2 of 46 START sql table model main.dim_order_flags ............................... [RUN]
3 of 46 START sql view model main.stg_events ..................................... [RUN]
4 of 46 START sql view model main.stg_orders ...................................... [RUN]
3 of 46 OK created sql view model main.stg_events ................................ [OK in 0.08s]
2 of 46 OK created sql table model main.dim_order_flags ........................... [OK in 0.09s]
1 of 46 OK created sql table model main.dim_date .................................. [OK in 0.09s]
4 of 46 OK created sql view model main.stg_orders ................................. [OK in 0.09s]
5 of 46 START sql view model main.stg_products .................................... [RUN]
6 of 46 START sql view model main.stg_stores ....................................... [RUN]
7 of 46 START snapshot main.dim_product_snapshot ................................... [RUN]
[WARNING]: Data type of snapshot table timestamp columns (TIMESTAMP) doesn't match derived column 'updated_at' (DATE). Please update snapshot config 'updated_at'.
6 of 46 OK created sql view model main.stg_stores .................................. [OK in 0.10s]
5 of 46 OK created sql view model main.stg_products ................................ [OK in 0.10s]
7 of 46 OK snapshotted main.dim_product_snapshot .................................... [OK in 0.16s]

[... 8 staging tests (inherited from module 2), all PASS ...]

20 of 46 START sql table model main.fact_sessions .................................. [RUN]
20 of 46 OK created sql table model main.fact_sessions .............................. [OK in 0.04s]
21 of 46 START sql table model main.dim_store ....................................... [RUN]
21 of 46 OK created sql table model main.dim_store .................................. [OK in 0.04s]
22 of 46 START sql table model main.dim_category ..................................... [RUN]
22 of 46 OK created sql table model main.dim_category ................................ [OK in 0.04s]

[... 6 tests over fact_sessions and dim_category, all PASS ...]

23 of 46 START sql incremental model main.fact_orders ................................ [RUN]
23 of 46 OK created sql incremental model main.fact_orders ........................... [OK in 0.06s]

[... 6 tests over fact_orders (module 4's) all PASS ...]

37 of 46 START sql table model main.fact_store_activity .............................. [RUN]
38 of 46 START sql table model main.mart_daily_sales_obt .............................. [RUN]
37 of 46 OK created sql table model main.fact_store_activity ......................... [OK in 0.03s]
38 of 46 OK created sql table model main.mart_daily_sales_obt ......................... [OK in 0.03s]

[... 8 final tests over fact_store_activity and mart_daily_sales_obt, all PASS ...]

Finished running 1 incremental model, 1 snapshot, 7 table models, 33 data tests, 4 view models in 0 hours 0 minutes and 0.67 seconds (0.67s).

Completed successfully

Done. PASS=46 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=46

46 resources, 46 PASS, zero errors. This is this run's real number — don't copy it from any earlier module: twelve models (seven that already existed, five new) plus one snapshot plus thirty-three data_tests (fifteen inherited from module 4, eighteen declared in this module's lesson 4) gives exactly 12 + 1 + 33 = 46.

Reading the execution order: what runs before what, and why

The Finished running 1 incremental model, 1 snapshot, 7 table models, 33 data tests, 4 view models line summarizes, by type, what you just saw — but the real order, line by line, is what's worth understanding:

  • dim_date, dim_order_flags, and the four staging views start first, in parallel. None depends on another model — dim_date generates its own date range, dim_order_flags declares its six fixed rows, and the four stg_* only depend on sources — so dbt schedules them as soon as the DAG allows, with no forced order between them.
  • dim_product_snapshot runs alongside the staging models, not before or after as a separate block. The same observation module 5 already made: the snapshot depends directly on source('kiosko_raw', 'products'), never on any model, so its position in the queue depends only on when the DAG allows it, not on any thematic order.
  • fact_sessions and dim_category run after their respective staging models (stg_events and stg_products), as soon as those dependencies finish.
  • fact_orders waits for dim_store and dim_date — its two model dependencies, besides stg_orders — exactly as module 3 already established.
  • fact_store_activity and mart_daily_sales_obt run last, because they're the graph's two deepest models: both depend on fact_orders, which in turn depends on dim_store and dim_date — three levels, the complete project's longest chain.

Not one step of this sequence was something you decided by writing an explicit order in any file — it's, start to finish, the consequence of the ref()s and source()s you kept declaring module by module, resolved automatically by dbt. That's, precisely, this whole guide's central promise, delivered over twelve models instead of one.

Confirming none of the earlier work broke

Before moving on to lesson 6, it's worth a quick check that expanding the project didn't affect anything that already worked:

dbt show --inline "select count(*) as n_rows, sum(revenue) as total_revenue from {{ ref('fact_orders') }}"

What to expect.

Previewing inline node:
| n_rows | total_revenue |
| ------ | -------------- |
|     40 |         106.15 |

The same 40 rows and 106.15 revenue since module 3 — adding five new marts, all of them consuming fact_orders via ref(), didn't change a single character of its own result.

Common mistakes

Running dbt build with no snapshot sequence replicated, over a freshly cloned project. What happens: someone jumps straight to this lesson's "The complete command" section, without having read the previous section, over a kiosko.duckdb that never existed before. Why it happens: the dbt build --vars '{"run_date": "..."}' command runs successfully anyway — PASS=46, zero errors — so there's no visible signal something went differently than expected. How to spot it: if you query dim_product_snapshot after this dbt build and see four rows instead of five, or P002 with a single version (health-snacks) instead of two, you're missing the history — exactly the same symptom module 5's mini-project's "Common mistakes" already described. How to fix it: if this happened to you, there's no single-command shortcut — reproduce the previous section's two-run dbt snapshot sequence, in that exact order (products_v1.csv first, products_v2.csv after), before running dbt build again.

Interpreting Finished running ... 33 data tests as a complete list of what got tested. What happens: someone sees the number 33 and assumes it exhaustively covers any possible problem in Kiosko's data. Why it happens: thirty-three tests feels like wide coverage, after a whole module (4) devoted to tests and eighteen more declared in this module. How to spot it: check which columns of which marts don't have any data_tests: declared — for example, no test yet validates that revenue_array_7d never has more than seven elements, or that session_date always comes before or on purchase_ts. How to fix it: the same lesson module 4 already taught — PASS=N tests exactly the N rules you declared, never "everything that could possibly be wrong" — thirty-three is the coverage this project chose, not a natural ceiling on the problem.

Assuming your own run's START/OK line order is going to be identical, line for line, to this lesson's. What happens: someone runs dbt build on their machine, sees a different interleaving order than this lesson's — for example, dim_category finishing before fact_sessions, the opposite of what the "What to expect" block shows — and worries something's wrong. Why it happens: Concurrency: 4 threads means several models run in parallel, and the exact order each one finishes in depends on non-deterministic factors — your machine's load at that moment, among others — even though the final number (PASS=46) is always the same. How to spot it: if your Done. PASS=46 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=46 matches this lesson's, your run was successful, regardless of what exact order the intermediate lines showed up in. How to fix it: dbt's guarantee isn't "the same order line by line" — it's that the order respects the graph's real dependencies (it never runs a model before its ref()s), and that the final result is the same no matter what exact order the engine chose to interleave the parallel threads in.

Exercises

Exercise 1 — Reproduce the complete dbt build from module 7's state. If you have access to a copy of the project as it was left at the end of module 7 (before this module's lesson 2), apply lesson 3's five new files and lesson 4's updated _models.yml, and confirm you land at exactly PASS=46 in the final dbt build.

See solution

If you followed lessons 2 through 5's sequence, the result should be exactly the same: dbt build --vars '{"run_date": "..."}' ends at PASS=46 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=46. If the number doesn't match, first count how many models the Found ... models line reports at the start of the run — it should say Found 12 models, 33 data tests, 1 snapshot, 4 sources, 502 macros; if it says a different number of models or tests, check that lesson 3's five .sql files and lesson 4's new _models.yml entries are all in place.

Exercise 2 — Trigger the missing run_date error, on purpose, and confirm it against what you already know from module 6. Run dbt build with no --vars at all over this module's complete project. How many ERRORs and how many SKIPs do you expect, compared with the ERROR=1, SKIP=7 you saw in module 6?

See solution

ERROR stays at exactly 1 — only fact_orders fails directly, from the same Compilation Error: Required var 'run_date' not found — but the SKIP count grows: besides the seven tests that already depended on fact_orders in module 6, now the models and tests that depend on fact_orders indirectly also end up in SKIPfact_store_activity, mart_daily_sales_obt, and all their own data_tests declared in this module's lesson 4. The mechanism is identical to the one module 3 already explained (cascading SKIP) and module 6 (the same specific error) — only now the cascade is longer, because the dependency graph grew.

Exercise 3 — Argue why dbt build is the right tool to close this guide, and not a manual sequence of dbt run + dbt snapshot + dbt test. In 2-3 sentences, using what you know since module 5, explain what extra guarantee dbt build gives you over running those three commands separately, in whatever order you decide.

See solution

dbt build doesn't just run the same three resource categories (models, snapshots, tests) you'd run separately — it runs them in the correct dependency order, respecting the complete DAG, and interrupts with SKIP any resource that depends on something that failed, instead of continuing to run over potentially incorrect data. If you ran dbt run followed by dbt snapshot followed by dbt test separately, you'd have to decide the right order between the three commands yourself — and if dbt run failed halfway through, dbt test would still try to run over half-built models, with no SKIP mechanism warning you. dbt build, with its unified resource-type selector, is the right way to close any complete run of this project, exactly as modules 5, 6, and 7's mini-projects already demonstrated.

Summary and next step

In this lesson you ran Kiosko's complete project, end to end, with a single command: dbt build --vars '{"run_date": "2026-08-09"}', ending at PASS=46 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=46 — twelve models, one snapshot, thirty-three data_tests. You read the real execution order, confirming every piece ran exactly when its ref() graph allowed it, with nobody having to declare that order by hand. And you confirmed fact_orders stays exactly the same — 40 rows, 106.15 revenue — after the project doubled in size.

Before moving on you should be able to: explain why fact_store_activity and mart_daily_sales_obt are, of the project's twelve models, the ones that run latest in the sequence; and reproduce, from memory, the two steps a freshly cloned project needs before its first complete dbt build.

Lesson 6 queries the final result: mart_daily_sales_obt, with revenue broken down by category and by store, reconciled against fact_orders — the same report data-modeling-for-analytics-guide produced by hand, now reproducible by anyone with the command you just ran.

Resources

  • dbt Developer Hub — "About dbt build," already cited in modules 5, 6, and 7, now confirmed over the complete twelve-model project. docs.getdbt.com/reference/commands/build. In English.
  • dbt Developer Hub — "Add snapshots to your DAG," already cited in module 5, the reference for why a freshly created snapshot has no history until its second run. docs.getdbt.com/docs/build/snapshots. In English.
  • dbt Developer Hub — "About incremental models," already cited in module 6, again confirming why --vars stays required once fact_orders exists as an incremental table. docs.getdbt.com/docs/build/incremental-models. In English.