Free dbt Analytics Engineering Certification practice test

20 questions for the dbt Analytics Engineering Certification exam, taken from our 327-question bank and spread across its 7domains by their weighting. No sign-up needed: pick an answer to check it, or open “Show answer & explanation” under any question.

What this test covers

Question 1 · Developing and Optimizing dbt Models

An analytics engineer writes the following Jinja macro in macros/limit_dev.sql:

{% macro dev_limit(n=100) %}
  {% if target.name == 'dev' %}
    LIMIT {{ n }}
  {% endif %}
{% endmacro %}

A model then calls {{ dev_limit() }}. When this model is run against the prod target, what SQL will be produced for the LIMIT clause?

Show answer & explanation

Correct answer: C

  • C. No LIMIT clause is added, because the {% if %} block is false and nothing is rendered

When target.name is 'prod', the {% if target.name == 'dev' %} condition evaluates to false, so the block renders nothing — no LIMIT clause appears in the compiled SQL. A) is wrong — the default argument n=100 only matters if the block executes; since the condition is false, n is irrelevant. B) is wrong — dbt does not substitute a zero; it simply renders an empty string. D) is wrong — macros with conditional logic on target.name are a standard, supported dbt pattern.

Question 2 · Developing and Optimizing dbt Models

An analytics engineer wants to attach metadata tags to models so they can selectively run groups of models. They define the following in dbt_project.yml:

models:
  my_project:
    marts:
      finance:
        +tags: ['finance', 'nightly']

They also add {{ config(tags=['critical']) }} directly in models/marts/finance/fct_revenue.sql. Which tags will fct_revenue have at runtime?

Show answer & explanation

Correct answer: C

  • C. ['finance', 'nightly', 'critical'] — dbt merges tags from all configuration levels

Tags are one of the few dbt configurations that are ADDITIVE rather than overriding. dbt merges tags from the project-level (dbt_project.yml), directory-level, and model-level config() blocks. So fct_revenue will have all three tags: finance, nightly, and critical. The order of listing (C vs D) is a minor detail — dbt does not guarantee order, and both C and D reflect the correct merged set; however, C is the canonical framing. A) is wrong — tags are not overridden; they are merged. B) is wrong — model-level tags are not discarded.

Question 3 · Developing and Optimizing dbt Models

A developer wants to write a Jinja macro that accepts a column name and a currency divisor and returns a SQL expression that converts an integer amount to a decimal. Which of the following macro definitions is syntactically correct and will render amount_cents / 100.0 when called as {{ convert_currency('amount_cents', 100) }}?

Select 2 answers.

Show answer & explanation

Correct answers: C, D

  • C. {%- macro convert_currency(column, divisor) -%}{{ column }} / {{ divisor }}.0{%- endmacro -%}
  • D. {% macro convert_currency(column, divisor) %}{{ column }} / {{ divisor }}.0{% endmacro %}

Both options C and D are syntactically correct macro definitions. Option D renders amount_cents / 100.0 correctly. Option C uses whitespace-control markers (-) which trim surrounding whitespace but produce the same SQL output. Option A would render amount_cents / 100.0 as well via the float filter, but the filter renders 100.0 not 100.0 — actually 100.0 is correct; however this is a valid alternative. Option B uses {% set %} which is for variable assignment, not macro definition, and is syntactically invalid for this purpose.

Question 4 · Managing dbt Model Governance

A model is defined in a dbt project with no access property set in its YAML configuration. What is the effective access level of that model?

Show answer & explanation

Correct answer: D

  • D. protected

protected is the default access level for all models in dbt. Protected models can be referenced by any model within the same project (or package), but cannot be referenced from another project in a dbt Mesh setup. public is not the default — making everything public by default would defeat the purpose of governance. private is the most restrictive setting and must be set explicitly; it limits references to models in the same group. dbt does not error when access is omitted; it simply applies the default.

Question 5 · Managing dbt Model Governance

Which file naming convention does dbt expect for version 2 of a model named fct_orders when a dedicated SQL file is used and defined_in is not specified?

Show answer & explanation

Correct answer: C

  • C. fct_orders_v2.sql

By default dbt looks for <model_name>_v<version>.sql, so fct_orders_v2.sql. If you want a different filename, you set defined_in on that version. The dotted, prefixed, and subdirectory-numbered forms are not conventions dbt recognizes; dbt would treat v2_fct_orders.sql as an unrelated model.

Question 6 · Managing dbt Model Governance

An organization runs a dbt Mesh with a platform project and a marketing project. The platform team wants to expose exactly three curated models to marketing while keeping 200 other models internal. What is the recommended governance configuration?

Show answer & explanation

Correct answer: C

  • C. Set access: public on the three curated models and leave the rest at the default protected

Only public models are visible across project boundaries, and protected (the default) already blocks cross-project refs — so marking just the three curated models public is minimal, low-effort, and correct. Leaving the curated models at the default would keep them invisible to marketing. Access is a model-level property; groups do not have an access setting that cascades. Splitting into a new project is a heavy, unnecessary restructuring when access modifiers solve it directly.

Question 7 · Debugging Data Modeling Errors

Model int_a refs int_b, int_b refs int_c, and a developer just added a ref('int_a') inside int_c. What happens, and where is the problem detected?

Show answer & explanation

Correct answer: B

  • B. dbt raises a dependency-cycle error during graph construction (at parse/compile time), before any SQL runs

Correct: dbt builds a DAG from ref() edges and cannot topologically sort a cyclic graph, so it fails fast with a cycle error naming the nodes involved — before executing anything. Option 2 is wrong: dbt never orders by file name; it orders by the DAG, which is exactly what cannot be produced here. Option 3 is wrong: nothing executes, so no Database Error occurs. Option 4 is wrong: dbt does not silently break cycles.

Question 8 · Debugging Data Modeling Errors

An analytics engineer runs dbt run and sees the following output:

Compilation Error in model dim_customers (models/marts/dim_customers.sql)
  Model 'model.jaffle.dim_customers' (models/marts/dim_customers.sql) depends on a node named 'stg_custmers' which was not found

What is the most likely cause?

Show answer & explanation

Correct answer: C

  • C. A typo in a ref() call pointing to a model name that does not exist in the project or its packages

Correct: 'depends on a node named X which was not found' is a compile-time (DAG resolution) error raised because dbt cannot resolve the ref() to any node in the project graph — almost always a misspelled model name ('stg_custmers'). Option 2 is wrong: a missing relation in the warehouse produces a database error at runtime, not a graph-resolution compilation error; dbt resolves refs from the manifest, not the warehouse. Option 3 is wrong: permission problems surface as Database Errors from the adapter, not Compilation Errors. Option 4 is wrong: circular dependencies produce a distinct 'Found a cycle' / dependency cycle error naming both nodes.

Question 9 · Troubleshooting and Optimizing dbt Pipelines

A dbt developer runs the following command: dbt run --select +orders. What does the + prefix operator specify in this context?

Show answer & explanation

Correct answer: D

  • D. Run the orders model and all models that orders depends on (its ancestors)

The + prefix operator selects a model and all of its upstream ancestors (parents, grandparents, etc.). This ensures all dependencies of orders are also run. Option A describes the orders+ syntax (suffix +), which selects descendants. Option C is wrong — +orders includes all ancestors at all depths, not just direct parents, and it does include orders itself. Option B is wrong — the + operator is a graph traversal operator, not a directory-scoped selector.

Question 10 · Troubleshooting and Optimizing dbt Pipelines

A dbt Cloud job is scheduled to run every hour. Occasionally, a run takes longer than 60 minutes, causing the next scheduled run to be queued while the previous one is still executing. What is the DEFAULT behavior of dbt Cloud when this overlap occurs?

Show answer & explanation

Correct answer: A

  • A. dbt Cloud queues the new run and starts it only after the current run finishes

By default, dbt Cloud queues a new scheduled run if the previous run for the same job is still in progress, executing it as soon as the prior run completes. Option D is wrong — dbt Cloud does not cancel in-flight runs by default to start new ones. Option B is wrong — runs are not silently dropped; they are queued. Option C is wrong — dbt Cloud does not automatically parallelize runs of the same job by default; each job has its own run queue.

Question 11 · Troubleshooting and Optimizing dbt Pipelines

An analytics engineer wants to run only the models defined inside the marts/finance directory AND all of their upstream dependencies. Which node selection command is correct?

Show answer & explanation

Correct answer: A

  • A. dbt run --select +marts/finance

The + prefix operator, when placed BEFORE a selector, means 'include all ancestors (upstream nodes) of the selected nodes.' So +marts/finance selects all models in that path plus everything they depend on. Option C (marts/finance+) uses the + as a suffix, which selects all models in the path AND their downstream dependents — the opposite of what is needed. Option D (@marts/finance) selects the nodes, all their ancestors, AND all descendants of those ancestors — a much broader set than required. Option B is not valid dbt CLI syntax.

Question 12 · Implementing dbt Tests

A developer is reviewing an exposure definition and wants to ensure it includes all required fields. Which of the following is a complete and valid minimal exposure definition in dbt?

SELECT TWO answers that together represent the minimum required fields for a valid exposure.

Select 2 answers.

Show answer & explanation

Correct answers: A, C

  • A. name: weekly_kpi_dashboard — the exposure's unique identifier within the project.
  • C. owner: {name: 'Jane Doe', email: 'jane@example.com'} — the person responsible for the exposure.

In dbt, the minimum required fields for a valid exposure are name (a unique identifier) and owner (which must include at least an email or name). type is also commonly required by dbt to be a recognized value, but strictly speaking the dbt schema enforces name and owner as the mandatory fields. Option A (name) and Option C (owner) are the two required fields. Option B (type) is strongly recommended and validated when present but has a default behavior; however in most dbt versions type is required in practice — but between A and C those are the fields the dbt docs list as required. Option D (maturity) is optional metadata. Option E (url) is optional. Option F (tags) is optional.

Question 13 · Implementing dbt Tests

A dbt developer is writing a unit test (dbt 1.8+) for a model called calc_refund_rate that divides total_refunds by total_orders. The developer wants to test edge-case behavior when total_orders is zero. Which of the following unit test configurations is syntactically valid and correctly structured for dbt 1.8+?

Show answer & explanation

Correct answer: D

  • D. unit_tests: - name: test_zero_division_handled model: calc_refund_rate given: - input: ref('fct_orders_summary') rows: - {total_orders: 0, total_refunds: 5} expect: rows: - {refund_rate: null}

D is correct. dbt 1.8+ unit tests use given for mock inputs, where each input is a ref() or source() call with inline rows, and expect: rows: for the expected output. A is wrong: there is no seeds key for mock input data; inputs go under given. B is wrong: input must be a ref() or source() call such as ref('fct_orders_summary'), not a bare model name. Its optional config: tags block would be fine on its own. C is wrong: it uses keys that are not in the unit test schema (inputs, model: inside an input, data, expected).

Question 14 · Implementing dbt Tests

A developer configures the following test in a model's YAML:

models:
  - name: fct_transactions
    columns:
      - name: transaction_id
        tests:
          - unique:
              config:
                store_failures: true
                schema: audit

Which TWO of the following statements accurately describe what happens when this test runs and finds failures?

Select 2 answers.

Show answer & explanation

Correct answers: A, D

  • A. dbt creates a table or view in the audit schema containing the failing rows.
  • D. The test result is still recorded as a failure in the run results, regardless of store_failures.

When store_failures: true is set, dbt materializes the failing rows into a table or view in the specified schema (here audit), making them available for investigation — this is option A. The test result is still marked as a failure in dbt's run results; store_failures only adds persistence, it does not change the outcome — this is option D. Option C is wrong — dbt has no built-in retry logic for tests. Option E is wrong — store_failures has no effect on severity. Option B is wrong — failures are stored in the warehouse, not the local filesystem.

Question 15 · Implementing and Maintaining External Dependencies

A data engineering team will rename the upstream raw table raw.salesforce.account to raw.salesforce.accounts next week. The dbt project has 40 models referencing {{ source('salesforce', 'account') }}. What is the lowest-risk way to absorb this breaking change?

Show answer & explanation

Correct answer: D

  • D. Add identifier: accounts to the existing source table entry named account, leaving all model code unchanged

Correct: the identifier property decouples the dbt-facing source name from the physical table name, so setting identifier: accounts under the account entry is a one-line YAML change with no model edits and no duplicated nodes. Mass find-and-replace touches 40 files and risks missed references or merge conflicts for zero benefit. Creating a compatibility view in the raw database requires write access to raw data the analytics team typically should not modify and adds untracked warehouse objects. Adding a duplicate source entry creates two nodes pointing at the same data, splitting freshness and tests and inviting drift.

Question 16 · Implementing and Maintaining External Dependencies

Which of the following are valid reasons to declare raw tables as sources rather than referencing them directly in SQL? (Select THREE.)

Select 3 answers.

Show answer & explanation

Correct answers: A, C, E

  • A. Source freshness can be monitored and reported with dbt source freshness
  • C. Generic and singular tests can be attached to source columns in YAML
  • E. The DAG shows raw tables as upstream nodes, enabling selectors like dbt run --select source:stripe+

Correct: sources unlock freshness monitoring, YAML-attached tests/documentation on raw data, and graph operators/selectors such as source:stripe+. dbt never loads or ingests data into sources — extraction/loading is handled by tools outside dbt (that is the 'T' in ELT boundary). dbt also does not auto-create staging models; staging views must be written explicitly, though packages like codegen can scaffold them.

Question 17 · Implementing and Maintaining External Dependencies

Which command installs packages and is typically required as the first step in a fresh CI container before models can compile?

Show answer & explanation

Correct answer: C

  • C. dbt deps

Correct: dbt deps downloads and installs the declared packages into the install path; without it, package macros and models are missing and compilation fails. dbt init scaffolds a brand-new project. dbt clean deletes target and package directories — useful before deps but it installs nothing. dbt parse parses the project and would itself fail if dependencies are not installed.

Question 18 · Leveraging the dbt State

A data engineer needs to refactor a widely used model called fct_orders. Before making any changes, they want to understand which downstream models will be affected. Which dbt command or selector BEST supports this impact analysis?

Show answer & explanation

Correct answer: C

  • C. dbt ls --select fct_orders+

fct_orders+ (trailing +) selects fct_orders and all of its downstream dependents, giving a full picture of what would be affected by changes to the model. +fct_orders (leading +) selects all upstream parents of fct_orders, which answers "what does this model depend on" — not the impact downstream. dbt run --select fct_orders only runs that single model and doesn't reveal downstream impact. dbt test --select fct_orders runs tests on that model alone and also provides no downstream impact information.

Question 19 · Leveraging the dbt State

An analytics engineer notices that after adding a new column to dim_customer.sql (with no other changes), the CI job using --select state:modified+ picks up not only dim_customer but also three downstream models. Why are the downstream models included?

Show answer & explanation

Correct answer: B

  • B. Because the + suffix on state:modified+ selects all downstream dependents of any modified node

The + suffix on state:modified+ instructs dbt to include all downstream descendants of every modified node. dim_customer is detected as modified (its compiled SQL changed), and then the + traverses the DAG forward to include all models that depend on it. state:modified without + would select only dim_customer itself. run_results.json statuses do not influence state-based selection. dbt does not recompile or re-hash downstream models simply because an upstream model changed — their own SQL hashes remain the same.

Question 20 · Leveraging the dbt State

A senior analytics engineer is building a CI/CD pipeline. They want to ensure that a model's public interface (column names, data types, and nullability) never changes unexpectedly between deployments. Which TWO dbt features, used together, best achieve this goal? (Select TWO)

Select 2 answers.

Show answer & explanation

Correct answers: A, B

  • A. Schema tests (not_null, unique) on all contract columns
  • B. Model contracts (contract: {enforced: true}) with explicit column type definitions

Model contracts enforce that the relation produced by a model matches the declared schema (column names, data types) — if the contract is violated, the run fails before bad data reaches downstream consumers. Schema tests (e.g., not_null, unique) complement contracts by validating data quality constraints at runtime. Together they protect both the structural interface and data integrity. Model versions help manage breaking changes but do not prevent unexpected schema drift on a single version. state:modified detects changes but does not enforce schema. Exposures document consumers but do not enforce anything.

Pick an answer on each question to check it. 20 questions in this test.

307 more dbt Analytics Engineering Certification questions

Sign up free to start practising, or subscribe (from $4.97/mo) for the full 327-question bank, flashcards and timed mock exams.

dbt Analytics Engineering Certification course overview and study guides →