CertSafari
    DBT Analytics Engineering· Lessons

    Domain 3 · Lesson 21/31

    Fixing a dbt model and testing the fix before merging

    Developing and implementing a fix and testing it prior to merging

    13 min read
    3.23% of exam
    6 sources
    Published 12 Oct 2026
    Docs as of 12 Oct 2026

    What you will be able to do

    • Reproduce a failure on a single model and confirm a patch fixes it
    • Select the patched model and everything downstream of it for a verification build
    • Add a unit test that keeps a fixed bug from coming back
    • Explain what a dbt CI job checks on a pull request before the fix is merged

    Key concept

    Fix-then-verify loop — Shrink the failure to the smallest thing that reproduces it, patch it, then rebuild and test the patched model and everything that depends on it, first in development and then in CI, before the change reaches production.

    1.Reproduce the failure on one model, then patch it

    A fix you cannot reproduce is a fix you cannot verify. Before you edit anything, cut the failure down to the smallest invocation that still shows it. Usually that means running only the model named in the error, and if the cause is still unclear, undoing the change that broke it. Once you know which error type you have and which file it points to (see [reading dbt error messages](lesson:3.1)), each further step either confirms your guess or rules it out.

    Database Errors need extra care, because the warehouse wrote the message and dbt only passes it on. In most cases the mistake is in the model's own SQL. Open the model and its compiled SQL side by side, as covered in [troubleshooting with compiled code](lesson:3.2). Then run the compiled query by itself in a query runner, or with the Preview button in the Studio IDE. If that query fails, the problem is in the SQL itself and not in how dbt runs it. Fix the model, then rerun the model that failed, not the whole project.

    Some failures come from queries dbt runs behind the scenes: hooks, schema creation, introspection, or the merge and insert statements of incremental models and snapshots. None of these appear in the model file, so logs/dbt.log is where you look. Its contents build up over many runs. Empty the file, re-execute dbt run for only the failing model, and the log then holds just the queries from that one attempt.

    Checkpoint 1 of 7· Put it in order

    A model fails with a Database Error. Put the fix-and-confirm steps in order.

    1. 1.Re-execute the compiled query on its own to isolate the error
    2. 2.Fix the mistake in the model
    3. 3.Open the model file next to its compiled SQL
    4. 4.Rerun the failed model

    Sources1

    2.Rebuild the patched model and everything downstream

    When the failed model builds again, you have proved only that one model works. A patch can rename a column, change the grain, or drop rows, and every child model reads that output. To check the patch properly, select the model and all its descendants. Put the + graph operator to the right of the model name: dbt build --select model_name+. With + on the left you get the model's ancestors instead, which rebuilds what feeds the model and leaves untested the models that depend on the fix.

    Selecting a model with its children, and with the parents of those childrenbash
    # select my_model and all of its children
    dbt run --select "my_model+"
    
    # select my_model, its children, and the parents of its children
    dbt run --select @my_model

    Use dbt build for this step, not dbt run. build runs seeds, models, snapshots and tests together in DAG order, so a data test on a child model checks your patch in the same command that builds it. When a node fails, dbt skips every node that depends on it, and those skips show you how far the problem spreads. To check the selection before spending warehouse time, dbt ls with the same selector prints the nodes it matches.

    Two cases need more than a plain rebuild. If you changed the logic of an incremental model, rows that were built earlier still hold the old result, so rebuild the model from scratch with --full-refresh. If the fix is a refactor that should not change any output, the dbt_audit_helper package compares the results of two queries so you can confirm the old and new versions return the same rows.

    Checkpoint 2 of 7· Check yourself

    You patched stg_orders. Which command verifies the patch against every model that consumes it, and runs their tests too?

    Checkpoint 3 of 7· Exam question

    A `stg_orders` model fails with a database error. You open the compiled file under `target/run/` and find the executed statement references a column named `oder_id` instead of `order_id`, a typo you introduced in the model's source `.sql` file. After correcting the typo, which command rebuilds this model together with every downstream model and test that depends on it, before you open a pull request?

    Sources23

    3.Lock the fix in with a unit test

    A successful rebuild proves the fix works on today's data. It does not stop the same bug from coming back the next time someone edits the model. Unit tests close that gap. A unit test feeds the model a small set of fixed input rows and compares its output with rows you wrote down in advance. It tests the model's SQL logic, not the data in the warehouse, and it runs before the model is materialized. dbt names logic that already had bugs reported as one of the clearest cases for a unit test. Edge cases you have not yet seen in real data, and work done just before a major refactor, are two more.

    For a fix, write the test from the bug report. Turn the input that broke the model into fixture rows, and use the correct result as the expected output. Before the patch, the test fails. After the patch, it passes. From then on it runs on every change to the model. The YAML structure for given and expect is covered in [unit tests and other test types](lesson:5.1). One limitation applies: unit tests support only SQL models in the current project.

    Unit tests belong in development and CI, not production. Their inputs are fixed, so running them in a production build spends compute and checks nothing new. Exclude them from production builds with --exclude-resource-type, and while you work, run only the unit tests with the test_type selector.

    Checkpoint 4 of 7· Fill the gap

    Complete the command that runs only unit tests while you iterate on a fix.

    dbt test --select " ? :unit"

    Checkpoint 5 of 7· Exam question

    You're troubleshooting a `fct_revenue` model that throws a SQL error only when dbt runs it, even though the raw SQL looks syntactically valid when pasted directly into your warehouse's query editor. Which techniques correctly help you determine whether the failure originates from dbt's Jinja and `ref()` rendering rather than from the underlying SQL itself? (Select all that apply)(Select 3)

    Sources4

    4.Let a CI job test the pull request

    Local checks run in your own development schema, against whatever is already there. A CI job repeats the check in a clean, shared environment before the merge. In dbt, a CI job runs whenever someone opens a pull request or pushes a commit to one. dbt Labs recommends putting the job in its own deployment environment connected to a staging database, so temporary CI schemas stay separate from production builds.

    The default command is dbt build --select state:modified+, sometimes called Slim CI. Rather than rebuilding the whole project, dbt builds and tests only the nodes your branch changed, plus their descendants. To decide what counts as changed, dbt compares your branch with the last successful run in a deferred environment, which is normally Production (see [state selection](lesson:7.1)). If no deferred environment is selected, there is nothing to compare against. In a setup that does not use state, the @ operator plays a similar role: it also builds the parents of your model's children, which will not exist yet in a fresh CI schema.

    CI job settings that decide what your pull request is tested against
    SettingWhat it controls
    Triggered by pull requestsOn by default; the job runs when a PR is opened or a commit is pushed to it
    CommandsDefaults to dbt build --select state:modified+ to build changed models and their downstream dependents
    Compare changes against an environment (Deferral)The environment whose state the PR code is compared with, by default Production
    dbt compareEnterprise option that compares production state with the PR changes; record-level comparison needs a primary key constraint or uniqueness test
    Run timeoutCancels the CI run if it exceeds the timeout, limiting warehouse use

    CI runs do not get in each other's way. Each pull request builds into its own schema, so ten developers testing ten fixes at once never overwrite each other's results, and dbt drops each schema when its PR is merged. CI jobs also never block a production run. Unlimited CI jobs can run at once. If you push a new commit while an older run is still in progress, dbt cancels the older run and starts again on the new code. A green check on the PR therefore means the latest version of your fix built and passed its tests together with everything downstream of it.

    Checkpoint 6 of 7· Check yourself

    Which statement about dbt CI jobs is FALSE?

    Checkpoint 7 of 7· Exam question

    A teammate's pull request fails during dbt's parsing step with a YAML compilation error. The file `models/staging/stg_customers.yml` reads: ```yaml models: - name: stg_customers columns: - name: customer_id tests: - unique - name: email tests: - not_null ``` Which line is causing the parsing failure?

    Sources523

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.dbt build --select "+my_model" verifies a patch against the models that consume my_model.Why is that wrong?

      A leading + selects ancestors. To include the downstream models that read the patched output, put the + after the model name.

      Covered in Rebuild the patched model and everything downstream

    2. 2.A unit test that guards a bug fix should run in every production build too.Why is that wrong?

      Unit tests use fixed inputs, so production runs add nothing. Run them in development and CI, and exclude them from production builds.

      Covered in Lock the fix in with a unit test

    3. 3.state:modified+ in a CI job can find the changed models without any other environment configured.Why is that wrong?

      dbt needs a comparison state to detect modifications, and it takes that state from the deferred environment, usually Production.

      Covered in Let a CI job test the pull request

    Practise it for real

    Take a broken model from failure to a verified fix ready for a pull request

    1. 1.Empty logs/dbt.log, then run dbt run --select for only the failing model

      Why: The log then holds only the queries from this one attempt

      You should see: The same error as before, and a short log ending with the failing query

    2. 2.Run the compiled query for the model on its own in a query runner or with Preview, then fix the model's SQL

      Why: Running the compiled SQL separately shows whether the error is in the SQL itself

      You should see: The corrected query runs without error

    3. 3.Run dbt build --select "my_model+", replacing my_model with the name of the patched model

      Why: Builds and tests the patched model and every descendant in DAG order

      You should see: All selected nodes pass, and none are skipped

    4. 4.Add a unit test reproducing the reported bug and run dbt test --select "test_type:unit"

      Why: Keeps the fixed logic from breaking again

      You should see: The unit test passes against the patched model

    5. 5.Commit, push and open a non-draft pull request

      Why: Triggers the CI job running dbt build --select state:modified+

      You should see: A CI check appears on the PR in your git provider within seconds

    Stuck? Get a nudge

    If the build skips child models, a node earlier in the selection failed. Fix that node first.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Then, re-execute dbt run for just the problematic model.”
      ↩︎ Reproduce the failure on one model, then patch it
      “Try to re-execute the SQL to isolate the error”
      ↩︎ Reproduce the failure on one model, then patch it
      “Isolate the problem — for example, by running one model a time, or by undoing the code that broke things.”
      ↩︎ Key concept
    2. 2.
      “If something fails, nodes that depend on it will be skipped.”
      ↩︎ Rebuild the patched model and everything downstream
      “Once each PR is merged, dbt will drop that schema.”
      ↩︎ Let a CI job test the pull request
      “it will be automatically canceled and replaced with the fresh code.”
      ↩︎ Prediction
      “CI jobs will never block a production run.”
      ↩︎ Checkpoint
    3. 3.
      “Use the --full-refresh flag to rebuild an incremental model from scratch.”
      ↩︎ Rebuild the patched model and everything downstream
      “Use it when refactoring existing logic to ensure that the new results are identical.”
      ↩︎ Rebuild the patched model and everything downstream
      “Use the @ operator on the left of a model in a non-state-aware CI setup to test it.”
      ↩︎ Let a CI job test the pull request
    4. 4.
      “Logic for which you had bugs reported before.”
      ↩︎ Lock the fix in with a unit test
      “We currently only support unit testing SQL models.”
      ↩︎ Lock the fix in with a unit test
      “dbt Labs strongly recommends only running unit tests in development or CI environments.”
      ↩︎ Exam trap 2
    5. 5.
      “This informs dbt to build only new or changed models and their downstream dependents.”
      ↩︎ Let a CI job test the pull request
      “Importantly, state comparison can only happen when there is a deferred environment selected to compare state to.”
      ↩︎ Exam trap 3

    Also cited

    Spotted a mistake, or was something unclear? Tell us.