Debugging data modeling errors: sample questions

4 free practice questions on debugging data modeling errors, one of the seven topics in the official v1.11 outline for the dbt Analytics Engineering Certification Exam — an estimated 16% of the exam, or about 11 of its 65 questions. Answers, reasoning and documentation links are all on this page.

Last updated . We revise these pages whenever dbt Labs revises the exam.

SHARE OF THE EXAM~16%our estimate
QUESTIONS HERE41 with runnable SQL
FORMATS SHOWN3of 6 on the exam

What this topic covers

Debugging is the second-largest topic on the exam. Its five subtopics are reading logged error messages, troubleshooting through compiled code, fixing .yml compilation errors, developing and testing a fix before merging it, and managing dbt's behaviour with flags.

SUBTOPICS, FROM THE OFFICIAL OUTLINE · 5

  1. 01Understanding logged error messages
  2. 02Troubleshooting using compiled code
  3. 03Troubleshooting .yml compilation errors
  4. 04Developing and implementing a fix and testing it prior to merging
  5. 05Managing dbt behavior with flags

Where the marks go

This domain rewards one habit above all others: reading the compiled SQL rather than the model file. The hotspot question below drops you into target/compiled/ hunting for the reference that resolved to the wrong schema, which is the actual debugging loop rather than a description of it.

The other recurring question is which layer an error came from. “Database Error” is dbt's label for something the platform rejected, which means parsing, the manifest, Jinja rendering and the connection all already succeeded — that narrows the search enormously, and it is what the first question is testing.

4 sample questions, with answers

Nothing is hidden. Each question shows the correct answer, why it is right, why every other option is wrong, and the documentation page that settles it. Where the answer is a claim about SQL behaviour, there is a query you can run in the browser against a small sample schema.

Q1Multiple choiceEasy

Your project has models/staging/stg_orders.sql. You misspell the ref in models/marts/fct_orders.sql as ref('stg_ordrs') and run dbt build in dev. The run ends with the log below. Which statement regarding this error is true?

09:14:02 Running with dbt=1.11.0
09:14:02 Registered adapter: postgres=1.11.0
09:14:03 Encountered an error:
Compilation Error
Model 'model.jaffle_shop.fct_orders' (models/marts/fct_orders.sql) depends on a node named 'stg_ordrs' which was not found
  • dbt executed the upstream models, then failed when it compiled fct_orders.
  • dbt aborted before executing any node, so nothing was built.CORRECT
  • dbt skipped fct_orders and completed the rest of the run.
  • The warehouse raised the error because the relation stg_ordrs does not exist.

WHY

Every ref() is resolved while dbt reads the project files and links the DAG, before any node is executed. The name 'stg_ordrs' matches no model in the project or its packages, so dbt raises a Compilation Error and exits the invocation right there — the log shows no node result lines and no end-of-run summary, because no model was ever built and no relation was created or replaced. The fix is a one-character edit in models/marts/fct_orders.sql followed by a re-run; nothing has to be cleaned up in the warehouse.

WHY THE OTHERS ARE WRONG

dbt executed the upstream models, then failed when it compiled fct_orders.
dbt resolves the refs for the whole selected graph up front, not model by model during execution. The failure happens while the graph is being linked, so stg_orders never reached the execution phase either.
dbt skipped fct_orders and completed the rest of the run.
SKIP is the status dbt assigns to downstream nodes when an upstream node fails or errors at run time. An unresolved ref is fatal to the whole invocation: dbt stops instead of continuing with the remaining nodes.
The warehouse raised the error because the relation stg_ordrs does not exist.
An error coming back from the data platform is reported as a Database Error and quotes the failing SQL or relation. This one says Compilation Error and names a node in the dbt graph — dbt never issued a query for fct_orders.
Q2HotspotMedium

You add unique and not_null tests for customer_id to models/staging/_stg_customers.yml. dbt parse succeeds, but dbt test --select stg_customers builds only one test. Click the region containing the mistake.

In the exam you click the region. The answer is highlighted below.

version: 2
models:
- name: stg_customers
description: One row per customer.
columns:
- name: customer_id
description: Primary key.
config:
meta:
owner: analytics
data_tests:
- unique
- not_null
- name: customer_status
description: Active or churned.
data_tests:
- accepted_values:
values: ['active', 'churned']
Correct region: lines 9–14.

WHY

data_tests is nested inside meta, and meta accepts an arbitrary dictionary of key-value pairs. dbt therefore stores the list {'data_tests': ['unique', 'not_null']} as metadata on the customer_id column instead of reading it as the test property, so no test nodes are created and nothing is malformed enough to raise a parse error. That is why the file parses cleanly yet only the accepted_values test on customer_status is built. The fix is to lift data_tests out of meta so it sits directly under the column entry, at the same indentation as description and config.

WHY THE OTHERS ARE WRONG

Line 1
version: 2 is a valid top-level property of a properties file and is optional in current dbt. It does not control where tests attach, and deleting it would not create the two missing test nodes.
Line 6
columns: correctly opens the list of column entries for stg_customers. Column-level tests are meant to live under this key — the block itself is well formed, and both column entries below it parse into real column properties.
Line 8
description: is a documentation property. It populates the column's description in the docs site and the manifest and has no effect on test collection, so the missing tests cannot be traced to this line.
Lines 18–20
This is the correctly placed test block: data_tests sits directly under the customer_status column entry, so accepted_values is parsed into a test node. It is the one test dbt actually built, which is the contrast that points at the customer_id column.
Q3HotspotMedium

You run dbt build in dev with target database analytics_dev and target schema dbt_alice, but fct_orders returns production customer data. In its compiled SQL, click the region whose relation kept the target schema but not the target database.

In the exam you click the region. The answer is highlighted below.

with __dbt__cte__int_order_flags as (
select order_id, is_return from analytics_dev.dbt_alice.stg_returns
),
orders as (
select * from analytics_dev.dbt_alice.stg_orders
),
payments as (
select * from raw.stripe.payments
),
customers as (
select * from analytics_prod.dbt_alice.dim_customers
),
final as (
select
orders.order_id,
customers.customer_segment,
__dbt__cte__int_order_flags.is_return,
payments.amount
from orders
left join customers using (customer_id)
left join payments using (order_id)
left join __dbt__cte__int_order_flags using (order_id)
)
select * from final
Correct region: lines 19–23.

WHY

analytics_prod.dbt_alice.dim_customers is the one relation whose two qualifiers were resolved by different rules, and that split is the diagnosis. The schema is dbt_alice — the dev target schema — so this reference did go through ref(); dbt interpolated the target schema exactly as it did for the other models. The database is analytics_prod, which is not target.database, so it can only have come from a database config on dim_customers (set on the model or on its path in dbt_project.yml). The default generate_database_name macro returns target.database when no custom database is set and the custom value trimmed and verbatim when one is, with no target prefix or suffix — unlike the default generate_schema_name, which concatenates the custom schema onto the target schema and so keeps every developer isolated. A hardcoded database config is therefore not environment-aware: dev reads production customers here, and because dbt resolves the same relation when it builds dim_customers, a dev run of that model writes into production too. Remove the database config, or override generate_database_name so it only honours the custom database on the prod target.

WHY THE OTHERS ARE WRONG

Lines 1–5
This is an ephemeral model inlined as a CTE — dbt compiles ephemeral refs into __dbt__cte__<model> blocks rather than selecting from a relation, so int_order_flags has no database or schema of its own to be wrong. The one relation inside it, analytics_dev.dbt_alice.stg_returns, carries the target database and the target schema.
Lines 7–11
analytics_dev.dbt_alice.stg_orders is the textbook dev resolution of ref('stg_orders'): both qualifiers come straight from the target, with no custom database or schema in play.
Lines 13–17
raw.stripe.payments is what source() compiles to — the database and schema declared in the sources YAML. Source locations are fixed by definition and deliberately are not rewritten per target, since raw landing data is not rebuilt for each developer. It also fails the condition in the question: the schema is stripe, not the target schema.
Lines 25–37
The final CTE selects only from CTE aliases defined earlier in the same statement. It names no warehouse relation, so there is no database or schema for dbt to have resolved here.
Q4MatchingMediumRunnable proof

You are standardizing how CI invokes dbt Core. Match each flag to what it changes about the invocation.

In the exam you pair each item yourself. The correct pairings are below.

ITEMMATCHES
--defer --state <path>Resolves refs to unbuilt upstream models against another environment
--store-failuresSaves failing test rows to a table in an audit schema
--threads 8Overrides the profile's concurrency for this invocation
--no-write-jsonSkips writing manifest.json and run_results.json to target/

WHY

Each flag changes one axis of the invocation, and three of the four change what dbt reads or writes rather than which nodes it builds. --defer --state <path> (l1 -> r4) changes ref() resolution: dbt resolves a ref using the state manifest only if the node isn't among the selected nodes and it doesn't already exist in the database, so a developer can build one model in a sandbox and point its parents at production relations. Deferral requires both flags — --defer alone has no manifest to defer to. --store-failures (l2 -> r1) changes what a test writes: instead of only reporting a failure count, dbt saves the records the test query returned into a table named after the test, in a schema that defaults to {{ profile.schema }}_dbt_test__audit; an explicit store_failures config on a test takes precedence over the flag, and each run replaces the previous failures for that test. --threads 8 (l3 -> r2) changes concurrency only: threads are the maximum number of paths through the graph dbt works on at once, and the flag overrides the number set on the target in profiles.yml for that invocation — it changes how fast the DAG is traversed, never which nodes are selected or how they are built. --no-write-json (l4 -> r3) turns off the WRITE_JSON config, so dbt still executes normally but does not serialize manifest.json, run_results.json, or the other JSON artifacts into target/ — useful when a step must not overwrite artifacts from a previous run, and costly when downstream tooling (docs, state comparison, dbt retry) needs those files.

WHY THE OTHERS ARE WRONG

Prefers the state manifest's relations over ones that exist in the database
This is --favor-state, a separate flag that modifies deferral rather than one of the four shown. Plain --defer uses the relation already in the database when the node exists there; --favor-state tells dbt to prioritize node definitions from the --state directory instead (except for nodes that are themselves selected).
Suppresses all non-error logs in standard out
This is --quiet (-q), which shows only error logs in stdout. It changes what you see in the terminal, not what dbt writes to disk — the common confusion is with --no-write-json, which sounds like 'less output' but controls JSON artifacts in target/, not log verbosity.

Know whether debugging data modeling errors is actually costing you marks.

The free readiness check is weighted like the real exam across all seven topics, so it tells you where you stand on this topic relative to the rest — which is the only version of that question worth answering before you book.

Take the free readiness check20 questions · ~15 minutes · no card

The other six topics

Keep reading

SOURCES

Every source above was read in full and last checked on .