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 runs 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:
arguments:
values: ['active', 'churned']
Correct region: lines 9–14.

WHY

data_tests is nested inside meta, and the meta config 'accepts any key-value pairs'. dbt therefore stores data_tests as just another metadata key on the customer_id column — an entry whose value is the list ['unique', 'not_null'], sitting alongside owner in the meta dictionary — instead of interpreting it as the column-level data_tests property. 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–21
This is the correctly placed test block: data_tests sits directly under the customer_status column entry, and the accepted_values test nests its values under the arguments property, the current form. It parses into the one test node 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 that satisfies the stem's condition: its schema is dbt_alice, the target schema, while its database is analytics_prod, not the target database. That split is exactly what dbt's default naming macros produce when a database is customized. The default generate_database_name returns '{{ custom_database_name | trim }}' when a custom database is supplied — the configured value verbatim, with no target prefix or suffix — because 'If the database configuration is supplied for a model, then dbt will build the model into the configured database.' The default generate_schema_name instead returns '{{ default_schema }}_{{ custom_schema_name | trim }}', appending any custom schema onto the target schema, which is what keeps developers isolated. So under the default macros, a database config on dim_customers (in the model or on its path in dbt_project.yml) yields precisely this compiled text — target schema kept, target database replaced — and the same qualifiers would follow from a generate_database_name override that returns analytics_prod. Either way the database is not environment-aware: dev reads production customers here, and a dev build of dim_customers writes into analytics_prod too. Remove the hardcoded database, or override generate_database_name so a custom database applies only 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 matches what source() compiles to — the full table name built from the database and schema declared for the source in YAML, not resolved from the target. 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 the state manifest
--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 unbuilt parents at the relations the manifest records. --defer alone supplies no manifest to defer to — a comparison manifest must come with it, through --state, through --defer-state ('If --defer-state is not specified, deferral will use the manifest supplied to --state'), or through the equivalent environment variables. --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 .