dbt Fundamentals Practice Questions (2026): 10 dbt Certification Questions with Answers
Ten exam-style questions across the dbt Analytics Engineering Certification topic areas — models, tests, Jinja, snapshots, and deployment — each with the correct answer and a plain-English explanation on click.
First, a clarification that trips up almost everyone searching this: “dbt Fundamentals” is a free, self-paced course from dbt Labs, not a certification. The credential you actually sit for is the dbt Analytics Engineering Certification — a 65-question, 2-hour, scenario-based, online-proctored exam that costs $200 USD and needs 65% to pass. It assumes SQL fluency and roughly six months of hands-on dbt. So “dbt fundamentals practice questions” really means: practice for the certification that tests those fundamentals. That is exactly what this page gives you.
Below are 10 free dbt practice questions written in the same style as the certification, spread across the exam’s topic areas (aligned to dbt version 1.11). Read each one, pick your answer, then hit Show answer & explanation. Keep score — there is a readiness check at the bottom.
10 dbt practice questions
Your model fct_orders.sql needs to read from another dbt model, stg_orders. Which reference builds the DAG correctly and resolves the right schema in every environment?
- Hardcode the table:
select * from analytics.stg_orders - Use the source function:
{{ source('stg_orders') }} - Use the ref function:
{{ ref('stg_orders') }} - Import it with
{{ import('stg_orders') }}
Show answer & explanation
{{ ref('stg_orders') }} tells dbt one model depends on another, so dbt builds them in the right order (the DAG) and substitutes the correct schema/database for your dev, CI, or prod target. Hardcoding (A) breaks the dependency graph and pins one environment. source() (B) is only for raw tables declared in a sources: block, not for dbt models. import() (D) is not a dbt function.
A raw table raw.jaffle_shop.orders is loaded by your ingestion tool, not by dbt. You declare it in a YAML sources: block. How should a staging model select from it so dbt can track freshness and lineage?
select * from {{ source('jaffle_shop', 'orders') }}select * from {{ ref('jaffle_shop', 'orders') }}select * from {{ seed('jaffle_shop_orders') }}select * from raw.jaffle_shop.orders
Show answer & explanation
source() takes the source name and the table name and resolves to the real relation, while wiring the raw table into lineage and enabling dbt source freshness. ref() (B) is only for dbt-managed models and seeds, not raw sources. seed() (C) is not a function — seeds are referenced with ref(). Hardcoding (D) works but loses lineage and freshness tracking.
A 600-million-row events model is rebuilt nightly. A full rebuild is slow and expensive, and new rows only arrive with a later event_timestamp. You want each run to process only new records. Which materialization fits best?
- view
- table (full refresh every run)
- incremental
- ephemeral
Show answer & explanation
An incremental model builds the full table once, then on later runs inserts/merges only rows that pass the filter in an {% if is_incremental() %} block (e.g. where event_timestamp > (select max(event_timestamp) from {{ this }})). A view (A) stores no data and re-computes on every query. A full table (B) is what you are trying to avoid. ephemeral (D) does not persist anything — it is inlined as a CTE.
A lightweight staging model is used only as an intermediate building block for other models. You do not want it to exist as its own table or view in the warehouse. Which materialization achieves this?
- table
- view
- incremental
- ephemeral
Show answer & explanation
Ephemeral models are not built as objects; dbt interpolates their SQL as a common table expression (CTE) into any downstream model that ref()s them. That keeps the warehouse tidy for small, reusable logic. A table (A) and view (B) both create a persistent database object, and incremental (C) also persists a table. Note: because ephemeral models are not materialised, you cannot query or test them directly.
You add this to a model’s YAML:columns: → - name: order_id → tests: [unique, not_null]. What does dbt do when you run dbt test?
- It rewrites the model to enforce a primary key constraint
- It runs two generic tests, each a SELECT that returns failing rows; the test passes when zero rows come back
- It silently drops duplicate and null rows from the table
- It only checks the column exists, not its values
Show answer & explanation
Generic tests like unique and not_null compile to a SQL query that selects the rows that violate the rule. Zero returned rows means the test passes; one or more means it fails. dbt does not add database constraints (A) or mutate your data (C) — tests are read-only assertions. And they check values, not mere existence (D).
Every customer_id in fct_orders must correspond to an existing row in the dim_customers model — a referential-integrity check. Which generic test enforces this?
accepted_valueswith the list of customer IDsuniqueoncustomer_idrelationshipswithto: ref('dim_customers')andfield: customer_id- A
not_nulltest oncustomer_id
Show answer & explanation
The relationships test is dbt’s built-in foreign-key check: it flags any customer_id in fct_orders that has no match in dim_customers. accepted_values (A) checks a column against a fixed hardcoded list, not another table. unique (B) catches duplicates, and not_null (D) catches nulls — neither verifies the value exists in a parent table.
The expression amount / 100 to convert cents to dollars is copy-pasted across a dozen models. You want one reusable, DRY definition you can call like {{ cents_to_dollars('amount') }}. What do you build?
- A seed CSV containing the conversion
- A macro defined with
{% macro cents_to_dollars(col) %}in themacros/folder - A snapshot that stores converted values
- A new source pointing at the dollar amounts
Show answer & explanation
A macro is a reusable Jinja function — {% macro cents_to_dollars(col) %} {{ col }} / 100 {% endmacro %} — that you call from any model, keeping the logic DRY and in one place. Seeds (A) load static CSV data. Snapshots (C) track slowly changing records over time. Sources (D) declare raw input tables. None of them templates reusable SQL logic.
You want a single command that runs your seeds, models, snapshots, and tests together in DAG order, stopping a node’s downstream builds if its tests fail. Which command should you use?
dbt rundbt testdbt compiledbt build
Show answer & explanation
dbt build runs seeds, models, snapshots, and their tests as one interleaved DAG, and it will not build a model’s children if that model’s tests fail. dbt run (A) executes models only (no tests or seeds). dbt test (B) runs tests only. dbt compile (C) just renders the SQL without touching the warehouse.
A source orders table mutates in place: the status column is overwritten from shipped to delivered with no history kept. You need a full Type 2 record of how each order’s status changed over time. Which dbt resource do you use?
- An incremental model with a
mergestrategy - A seed reloaded nightly
- A snapshot
- A view over the source table
Show answer & explanation
A snapshot implements slowly changing dimension (SCD) Type 2: on each run dbt compares the source to the stored snapshot and, when a tracked column changes, closes the old row and inserts a new one with dbt_valid_from/dbt_valid_to timestamps — giving you full history. An incremental model (A) appends new rows but does not version changes to existing keys. A seed (B) is static CSV. A view (D) always shows only the current mutated value, with no history.
Your dbt Cloud CI job should be fast: on each pull request, build and test only the models that changed plus everything downstream of them, deferring unchanged models to production. Which selection uses dbt state to do this (“Slim CI”)?
dbt build --full-refreshdbt build --select state:modified+ --defer(against the prod manifest)dbt build --select tag:nightlydbt run --select +fct_orders
Show answer & explanation
Slim CI compares the PR against a production manifest.json: state:modified selects changed nodes, the trailing + adds their downstream dependencies, and --defer lets unchanged parents resolve to the existing prod tables instead of rebuilding them. --full-refresh (A) rebuilds everything — the opposite of slim. tag:nightly (C) is a static selector unrelated to what changed. +fct_orders (D) selects one model’s upstream lineage, not what the PR modified.
What these questions cover
The 2026 exam (dbt version 1.11) is scenario-based across eight topic areas. Importantly, dbt Labs does not publish an official per-topic percentage weight — every question carries equal weight. The bars below are our estimated emphasis, based on the official study guide and where hands-on dbt work concentrates, so you can prioritise. Treat them as a study heat-map, not vendor figures.
Score yourself
Count how many of the 10 you got right before revealing the answer. Then read the band you land in honestly — the goal is a real pass, not a good feeling. Remember the live exam needs 65%, which is roughly 42 of 65 questions.
Want the full dbt question bank?
These 10 are a taster. Practise hundreds more exam-style dbt questions with explanations and timed mocks, then dig into the full topic breakdown on the exam guide.
dbt certification FAQ
Is dbt Fundamentals a certification?
No. dbt Fundamentals is dbt Labs’ free, self-paced intro course. The paid credential is the dbt Analytics Engineering Certification — 65 questions, 2 hours, 65% to pass, $200 USD. If you searched “dbt fundamentals practice questions,” you almost certainly want to prep for that certification, which is what these questions target.
How many questions are on the exam and what score do I need?
65 scored multiple-choice questions in 2 hours (120 minutes), and you need 65% or higher to pass. Every question carries equal weight. A few unscored research questions are mixed in unmarked; they do not count. You get your result immediately after finishing.
What dbt version and topics does it test in 2026?
The exam is aligned to dbt version 1.11 and is scenario-based across eight topics: developing and optimising models, model governance, debugging modeling errors, troubleshooting pipelines, implementing tests, external dependencies, and leveraging dbt state. It assumes SQL fluency and about six months of hands-on dbt (Cloud or Core).
Are practice questions enough to pass?
They are essential but not sufficient. The exam is scenario-based — build a real dbt project, run dbt build, write tests and a snapshot, wire up sources and a package, and set up a CI job. Use practice questions to find and close weak topics, not to replace hands-on reps.
ExamCert Team — we build exam-style practice banks and prep apps for 90+ IT certifications. Questions here are original, written to match the dbt Analytics Engineering Certification objectives; they are not real exam items.
Related: dbt Analytics Engineering exam guide · Free practice tests
