CertSafari

    Free DBT Analytics Engineering Sample Questions

    35 free sample questions from our bank of 341+, covering every exam domain, with answers and detailed explanations. Updated September 2026.

    Domain 1: Developing and optimizing dbt models

    Subdomain 1.3: Conceptualizing modularity and how to incorporate DRY principles

    1.Which command must be run after adding a new entry to `packages.yml` and before that package's macros or models become available in the project?

    1. A.dbt deps
    2. B.dbt compile
    3. C.dbt docs generate
    4. D.dbt seed
    Show answer & explanation

    Correct answer: A — dbt deps

    • A. This is correct because this command reads `packages.yml`, downloads each listed package into the `dbt_packages` directory, and writes the lock file, which is what makes the package's macros and models resolvable.
    • B. This is incorrect because this command only renders the project's Jinja into executable SQL; it does not fetch packages, so a package added to `packages.yml` but never installed would still fail to resolve.
    • C. This is incorrect because this command builds the documentation website and catalog from already-compiled project metadata; it has no role in fetching or installing package dependencies.
    • D. This is incorrect because this command loads CSV files in the `seeds` directory into the warehouse as tables; it is unrelated to installing packages declared in `packages.yml`.

    Subdomain 1.1: Identifying and verifying any raw object dependencies

    2.Which of the following statements accurately describe how dbt's `source()` function affects dependency resolution and the project DAG? (Select all that apply.)(Select 3)

    1. A.`source()` calls add source nodes as DAG entry points, letting `dbt run --select source:name+` execute every downstream model in one command.
    2. B.`source()` requires the table to be declared under a `sources:` key in a YAML properties file before the Jinja function can resolve it during compilation.
    3. C.`source()` automatically creates the underlying raw table in the warehouse if it does not already exist when the model is executed.
    4. D.`source()` and `ref()` compile to the same generic reference, so dbt cannot distinguish a raw table dependency from a model dependency at runtime.
    5. E.`source()` enables freshness monitoring through `dbt source freshness`, which is not available for tables referenced through `ref()`.
    6. F.`source()` calls bypass dbt's dependency graph entirely and compile to plain table names with no DAG registration.
    Show answer & explanation

    Correct answers: A, B, E — `source()` calls add source nodes as DAG entry points, letting `dbt run --select source:name+` execute every downstream model in one command.; `source()` requires the table to be declared under a `sources:` key in a YAML properties file before the Jinja function can resolve it during compilation.; `source()` enables freshness monitoring through `dbt source freshness`, which is not available for tables referenced through `ref()`.

    • A. Declaring a source creates a graph entry point, and the `source:name+` selection syntax lets a team run or test everything downstream of that raw object in a single invocation.
    • B. A table must first be documented under a `sources:` key in a properties YAML file; without that declaration dbt has no source node to resolve when the Jinja function is compiled.
    • C. dbt never creates or manages the physical raw table behind a source declaration; a source only describes data that an external loader has already placed in the warehouse.
    • D. The two functions resolve differently: `ref()` points at a model's configured schema and table, while `source()` points at the exact table declared in the source YAML, and dbt tracks them as distinct node types in the manifest.
    • E. Freshness thresholds, `warn_after`/`error_after`, and the `dbt source freshness` command are only available for objects declared as sources; there is no equivalent freshness check for models referenced through `ref()`.
    • F. The opposite is true: `source()` explicitly registers the raw table as a node in dbt's dependency graph, which is exactly what lets lineage, selection syntax, and freshness checks recognize it.

    Subdomain 1.2: Understanding core dbt materializations

    3.A team needs to assert that no `orders` row has a `discount_pct` greater than 100, a business rule specific to this one model and unlikely to be reused elsewhere. Which testing approach best fits this requirement?

    1. A.Write a singular test as a standalone SQL file under `tests/` that selects rows where `discount_pct` exceeds 100 and fails the build if any rows are returned.
    2. B.Add a generic `accepted_values` test on the `discount_pct` column in the model's YAML properties file, listing every valid percentage as an accepted value.
    3. C.Write a custom generic test macro under `tests/generic/` and apply it to `discount_pct` in the YAML properties file so other models can reuse the same check.
    4. D.Add the built-in `relationships` test on `discount_pct`, pointing it at a reference table that lists every valid discount percentage between 0 and 100.
    Show answer & explanation

    Correct answer: A — Write a singular test as a standalone SQL file under `tests/` that selects rows where `discount_pct` exceeds 100 and fails the build if any rows are returned.

    • A. A singular test is exactly a one-off SQL assertion scoped to a single model, which matches a business rule that is specific to `orders` and not expected to be reused elsewhere.
    • B. `accepted_values` enumerates a fixed, finite list of valid values, which is unworkable for a continuous numeric range like every percentage from 0 to 100.
    • C. Building a reusable generic test macro adds abstraction for a rule the team has already said is unlikely to be reused, which is unnecessary overhead for what is really a one-off check.
    • D. `relationships` verifies that values in one column exist in another table's column, which tests referential integrity, not whether a numeric value falls within a valid range.

    Subdomain 1.4: Using commands such as build, run, test, docs, show, snapshot, and seed

    4.To validate a model's SQL and confirm its dependency graph compiles correctly against the warehouse — without reading a single row of data from any upstream `ref` or `source` — an analytics engineer should run `dbt build` with the ____ flag.

    1. A.--empty
    2. B.--sample
    3. C.--defer
    Show answer & explanation

    Correct answer: A — --empty

    • A. This flag runs models against zero-row versions of every `ref` and `source`, which validates that the SQL compiles and the DDL is correct without processing any actual data, matching the stated goal exactly.
    • B. This flag filters `ref`/`source` inputs down to a time-based subset of rows based on an `event_time` config, but it still reads real data rather than validating structure with zero rows.
    • C. This flag resolves unselected nodes against artifacts from a previous run in another environment, which is unrelated to limiting how much data a query reads — it addresses missing upstream nodes, not row volume.

    Subdomain 1.6: Defining configurations in dbt_project.yml

    5.An engineer is adding a directory-scoped override under the `models:` key in `dbt_project.yml` and needs the correct syntax so dbt treats it as a config rather than an arbitrary YAML key. Complete the rule: nested resource config keys inside `dbt_project.yml` must start with the ___ character.

    1. A.+
    2. B.@
    3. C.$
    Show answer & explanation

    Correct answer: A — +

    • A. This is the required prefix for resource configs nested by directory path directly inside dbt_project.yml, distinguishing a config key from an ordinary YAML key.
    • B. This character has no special meaning for resource configs in dbt_project.yml and would leave the key unrecognized.
    • C. This character is used for Jinja variable interpolation in templates, not for marking a nested config key in dbt_project.yml.

    Subdomain 1.7: Using dbt Packages

    6.A project's packages.yml currently contains only static Hub and git package entries with no Jinja templating. The team also wants to declare a cross-project ref for a dbt Mesh workflow in the same dependency-declaration file. Which change correctly supports both needs?

    1. A.Keep packages.yml as-is and add the Mesh reference there, since packages.yml was designed specifically to declare cross-project dependencies for dbt Mesh.
    2. B.Add the Mesh reference to dbt_project.yml under a `dependencies:` block, since project-level configuration files are where cross-project refs belong.
    3. C.Rename packages.yml to dependencies.yml, since that file supports both package installs and cross-project dbt Mesh references but drops support for Jinja templating.
    4. D.Create a second file named mesh.yml alongside packages.yml, since dbt reads dependency declarations from every YAML file whose name ends in `.yml`.
    Show answer & explanation

    Correct answer: C — Rename packages.yml to dependencies.yml, since that file supports both package installs and cross-project dbt Mesh references but drops support for Jinja templating.

    • A. Incorrect. packages.yml is built for package installs; dbt Mesh cross-project refs are a dependencies.yml concept, not something packages.yml was designed to declare.
    • B. Incorrect. dbt_project.yml holds project configuration, not dependency declarations, and has no `dependencies:` block for cross-project refs.
    • C. Correct. Because the file has no Jinja, it can be safely renamed to dependencies.yml, which supports both package syntax and dbt Mesh cross-project references, at the cost of Jinja support.
    • D. Incorrect. dbt does not scan arbitrary YAML filenames for dependency declarations; only packages.yml and dependencies.yml are recognized for this purpose.

    Subdomain 1.9: Providing access to users to models with the "grants" config

    7.A dbt project sets a project-level default in `dbt_project.yml`: ```yaml models: +grants: select: ['analyst_role', 'bi_role'] ``` A specific model's SQL file then adds: ```sql {{ config(grants = {'select': ['reporting_role']}) }} ``` After the next run, which grantees actually hold the `select` privilege on that model?

    1. A.Only `reporting_role` holds `select`, because a model-level grants config replaces the project-level default for that privilege rather than merging with it.
    2. B.All three roles hold `select`, because dbt automatically unions every grants definition that targets the same privilege across config levels.
    3. C.Only `analyst_role` and `bi_role` hold `select`, because a project-wide default in `dbt_project.yml` always outranks a model's inline config block.
    4. D.No roles hold `select`, because defining the same privilege in two separate locations makes dbt skip applying that privilege on the next run.
    Show answer & explanation

    Correct answer: A — Only `reporting_role` holds `select`, because a model-level grants config replaces the project-level default for that privilege rather than merging with it.

    • A. The default grants behavior is merge-and-clobber: a more specific config for the same privilege replaces the less specific one instead of combining with it. Since the model's inline config redefines `select`, it overrides the project-level list entirely.
    • B. dbt does not union grantee lists across config levels by default; the plain privilege key always replaces rather than merges. Automatic unioning only happens when the privilege key is prefixed with `+`, which this example does not use.
    • C. Project-level defaults in `dbt_project.yml` are the least specific config location, so a model's own config block always wins for that privilege when both define it. This is the reverse of what actually happens here.
    • D. dbt does not skip a privilege just because it is set in more than one place; it resolves the conflict deterministically by specificity. The model still ends up with a concrete grantee list after the run.

    Subdomain 1.9: Providing access to users to models with the "grants" config

    8.According to dbt's `grants` config reference, which set of resource types can have a `grants` config applied directly?

    1. A.Models, seeds, and snapshots
    2. B.Models, seeds, and sources
    3. C.Models, tests, and macros
    4. D.Models, exposures, and metrics
    Show answer & explanation

    Correct answer: A — Models, seeds, and snapshots

    • A. The grants config reference lists models, seeds, and snapshots as the resource types that build database objects and can carry a `grants` config. These are the three that dbt actually applies GRANT/REVOKE statements for after a run.
    • B. Sources are references to tables dbt does not create, so there is nothing for dbt to grant access on; only seeds, not sources, support the grants config. This pairing mixes a supported and an unsupported resource type.
    • C. Tests and macros are not database objects with their own access permissions, so the grants config has no meaning for them. Only models, seeds, and snapshots build queryable objects that grants apply to.
    • D. Exposures and metrics are metadata-only resources that describe downstream usage or calculations; they do not correspond to database objects that a warehouse can grant privileges on. The grants config does not apply to either of them.

    Subdomain 1.10: Creating snapshots in YAML

    9.A developer commits this snapshot definition and `dbt snapshot --select subscriptions_snapshot` fails to compile: ```yaml snapshots: - name: subscriptions_snapshot relation: source('billing', 'subscriptions') config: schema: snapshots unique_key: subscription_id strategy: check updated_at: modified_at ``` Which config key is missing and required to make this compile?

    1. A.check_cols
    2. B.strategy
    3. C.relation
    4. D.unique_key
    Show answer & explanation

    Correct answer: A — check_cols

    • A. The check strategy requires `check_cols`, listing which columns dbt should compare between runs (or `all` to compare every column). Without it, dbt has no way to know what to check for a change.
    • B. `strategy` is already present in the config block, set to `check`, so it is not the missing key.
    • C. `relation` is already present at the top level, pointing at the `subscriptions` source, so it is not missing.
    • D. `unique_key` is already present in the config block, set to `subscription_id`, so it is not the missing key.

    Subdomain 1.13: Running models in sample mode using the --sample flag

    10.A data engineer needs to validate `fct_incidents` against a specific historical incident window rather than a rolling window: every event between 2024-07-01 00:00:00 and 2024-07-08 18:00:00. Which command samples exactly that fixed window?

    1. A.`dbt run --select fct_incidents --sample="{'start': '2024-07-01', 'end': '2024-07-08 18:00:00'}"` filters to that literal date range regardless of when the command runs.
    2. B.`dbt run --select fct_incidents --sample="7 days"` filters to a rolling window measured backward from the moment the command executes.
    3. C.`dbt run --select fct_incidents --vars '{"start_date": "2024-07-01", "end_date": "2024-07-08"}'` passes plain vars that the sample filter never reads or applies.
    4. D.`dbt build --select fct_incidents --sample="1 week"` also filters a rolling window anchored to execution time rather than the fixed dates requested.
    Show answer & explanation

    Correct answer: A — `dbt run --select fct_incidents --sample="{'start': '2024-07-01', 'end': '2024-07-08 18:00:00'}"` filters to that literal date range regardless of when the command runs.

    • A. This uses the static time spec form of the sample flag, which takes explicit start and end values and filters refs and sources to exactly that range no matter when the command is run. That matches the requirement for a fixed historical window rather than a moving one.
    • B. A bare duration string like "7 days" is a relative spec: it counts backward from the current run time, so the window it produces shifts every time the command executes. It cannot reproduce a specific historical date range on demand.
    • C. Vars are just key-value inputs a model's Jinja can read if it was written to expect them; the sample flag's parser does not consume vars at all, so this would leave the model completely unsampled.
    • D. Like the other relative spec, "1 week" is measured back from execution time rather than fixed calendar dates, so it drifts with every run and does not target the specific July window requested.

    Subdomain 1.12: Validating model logic and schema definitions in dry-runs using the --empty flag

    11.A team wants to add a lightweight CI check that catches broken `ref()`/`source()` dependencies and invalid column types in new models before merging, without waiting on a full warehouse build of every affected table. Which statements about using `--empty` for this purpose are correct? (Select 2)(Select 2)

    1. A.Generic tests configured on the affected models will still validate real data quality issues, since `--empty` only changes how documentation is generated.
    2. B.Running `dbt build --empty` against the changed models still executes each model's compiled SQL against the warehouse, so genuine SQL syntax and type errors are still caught.
    3. C.Any Python model included in the changed set is automatically skipped by dbt when `--empty` is passed, so the CI job reports it as passed without running it.
    4. D.Because `ref()` and `source()` calls resolve to zero rows, the CI job avoids scanning or processing the full volume of production-scale data.
    5. E.The CI job can reuse the exact same command unmodified against incremental and Python models, since `--empty` behaves identically across every materialization type.
    Show answer & explanation

    Correct answers: B, D — Running `dbt build --empty` against the changed models still executes each model's compiled SQL against the warehouse, so genuine SQL syntax and type errors are still caught.; Because `ref()` and `source()` calls resolve to zero rows, the CI job avoids scanning or processing the full volume of production-scale data.

    • A. Once the referenced tables are built with zero rows under `--empty`, generic tests running against them would see empty data and largely pass trivially, so this statement overstates what the tests would actually catch.
    • B. This is correct: `--empty` still compiles and executes each model's SQL against the target warehouse, so a broken `ref()`, a typo, or an incompatible column type still surfaces as a real error.
    • C. dbt does not skip Python models under `--empty`; the flag is simply ignored for them, so a Python model in the selection still runs against full data rather than being marked as passed without execution.
    • D. This is correct: limiting every dependency to zero rows means the warehouse never has to scan or process the production-scale data behind those references, which is what keeps the CI check fast and cheap.
    • E. The flag does not behave identically across materializations; it is ignored for Python models, so a CI script cannot assume uniform zero-row behavior without accounting for that exception.

    Subdomain 1.14: Understanding advanced dbt materializations such as microbatch

    12.A dbt project has a `fct_iot_readings` model that ingests billions of sensor readings per day into a Snowflake warehouse. New readings sometimes arrive up to 48 hours late, occasional runs fail partway through, and the team wants to reprocess only the time slices that failed without touching prior successful loads. Which materialization approach best fits these requirements?

    1. A.Configure the model as `incremental` with the `microbatch` strategy, setting `event_time`, `batch_size`, and a `lookback` window so late readings are recaptured and failed batches retry independently.
    2. B.Configure the model as `incremental` with the `merge` strategy and a wide `incremental_predicates` window so every run rescans enough history to catch readings that arrive late.
    3. C.Configure the model as a `table` materialization and rely on a full rebuild each run so that late-arriving readings and any partial failures are automatically corrected together.
    4. D.Configure the model as `ephemeral` and reference it from a downstream `incremental` model so the sensor logic stays reusable while the downstream model absorbs late data and retries.
    Show answer & explanation

    Correct answer: A — Configure the model as `incremental` with the `microbatch` strategy, setting `event_time`, `batch_size`, and a `lookback` window so late readings are recaptured and failed batches retry independently.

    • A. The microbatch strategy divides processing into independent, time-bounded batches keyed on `event_time`, and a `lookback` window reprocesses recent batches so late-arriving sensor readings get captured. Because each batch is atomic, a failed run can be resolved with `dbt retry` without rerunning batches that already succeeded.
    • B. The merge strategy updates and inserts rows by `unique_key` in a single statement against the whole filtered set, so a wide `incremental_predicates` window only widens one scan rather than isolating and retrying individual failed time slices. It does not give the batch-level atomicity or selective retry the scenario needs.
    • C. Rebuilding the full table every run reprocesses billions of rows regardless of which slices actually failed, which defeats the goal of cheaply reprocessing only failed time windows. It also does nothing special to capture late-arriving data beyond brute-force reprocessing.
    • D. An ephemeral model is inlined as a CTE into whatever references it and cannot itself be built, queried, or retried independently, so it cannot provide batch-level retry behavior. Pushing the incremental logic downstream does not change how the late-arriving readings are batched or recovered.

    Subdomain 1.14: Understanding advanced dbt materializations such as microbatch

    13.A microbatch model on Databricks currently runs its batches sequentially, and the team wants dbt to run independent batches concurrently during `dbt run` without changing any warehouse settings outside the model config. Which single action accomplishes this?

    1. A.Set `full_refresh: false` in the model's config block so `dbt run --full-refresh` invocations are ignored and batches always run against the incremental table.
    2. B.Set `concurrent_batches: true` in the model's config block so dbt overrides its auto-detection and executes independent batches in parallel during the run.
    3. C.Set `lookback: 0` in the model's config block so dbt stops reprocessing prior batches and only executes the single most recent batch on every run.
    4. D.Set `on_schema_change: sync_all_columns` in the model's config block so new and removed columns are reconciled automatically before each batch executes.
    Show answer & explanation

    Correct answer: B — Set `concurrent_batches: true` in the model's config block so dbt overrides its auto-detection and executes independent batches in parallel during the run.

    • A. `full_refresh: false` only changes whether the `--full-refresh` flag is honored for the model; it has no bearing on whether batches execute sequentially or in parallel during a normal run.
    • B. `concurrent_batches` is the config that overrides dbt's automatic detection of parallel-execution safety, letting independent, non-overlapping batches run concurrently instead of one at a time. This directly changes the execution behavior the team wants.
    • C. `lookback` controls how many prior batches are reprocessed to catch late-arriving data on each run; setting it to zero changes which time ranges get reprocessed, not whether batches run in parallel.
    • D. `on_schema_change` governs how dbt reacts to column additions or removals in an incremental model's source data; it has no relationship to batch scheduling or concurrency during execution.

    Subdomain 1.8: Creating Python Models

    14.Since dbt Python models do not surface output from `print()` statements the way an interactive script would, what does dbt recommend for inspecting intermediate values while developing a model?

    1. A.Write the debug value into a new column on the DataFrame itself, so it can be inspected by querying the model's materialized output or previewing it during local development.
    2. B.Wrap the value in a Jinja `{{ log() }}` call placed at the top of the Python file, since dbt's Jinja logging layer also captures output emitted from compiled Python models.
    3. C.Run `dbt debug` against the model's alias immediately after `dbt run`, since that command streams the runtime variable values captured during the most recent execution.
    4. D.Insert a `raise Exception()` call that carries the value as its message so the run aborts immediately and the value surfaces directly in the terminal's traceback output for review.
    Show answer & explanation

    Correct answer: A — Write the debug value into a new column on the DataFrame itself, so it can be inspected by querying the model's materialized output or previewing it during local development.

    • A. Correct: since `print()` output is not surfaced, dbt's documented workaround is to write debug information into an extra DataFrame column so it can be inspected by querying the resulting table or previewing the DataFrame locally.
    • B. Incorrect: Python model files are not processed through dbt's Jinja engine, so `{{ log() }}` syntax has no effect when placed in a `.py` file; Jinja logging applies to SQL model compilation, not Python model execution.
    • C. Incorrect: `dbt debug` checks the project's connection and configuration health; it does not stream runtime variable values from a specific model's most recent execution.
    • D. Incorrect: deliberately raising an exception to inspect a value would fail the run and is not a documented or idiomatic debugging technique for Python models.

    Subdomain 1.11: Selecting the optimal incremental strategy based on a dataset's characteristics

    15.A BigQuery model is partitioned by `order_date` and typically only the most recent two days of partitions need correction when upstream data is reprocessed. The table has no reliable single-column unique key because a legitimate business key can appear more than once per day. Which incremental strategy fits best?

    1. A.`insert_overwrite`, configured with `partition_by` on `order_date`, so dbt replaces only the affected date partitions with freshly computed data.
    2. B.`merge`, configured with `unique_key: order_date`, so dbt matches rows by that date and updates any columns that changed for the corrected days.
    3. C.`delete+insert`, configured with `unique_key: order_date`, so dbt deletes all rows for the affected dates before reinserting the corrected data.
    4. D.`append`, run daily without a `unique_key`, so dbt inserts the reprocessed rows alongside the original rows already loaded for each corrected date.
    Show answer & explanation

    Correct answer: A — `insert_overwrite`, configured with `partition_by` on `order_date`, so dbt replaces only the affected date partitions with freshly computed data.

    • A. This is correct because insert_overwrite replaces whole partitions without needing a row-level unique key, which fits a table where the business key is not unique per day.
    • B. This is incorrect because using `order_date` as the unique key would treat every row for a given day as the same record, collapsing distinct rows with the same date into one.
    • C. This is incorrect for the same reason: matching on `order_date` as a unique key would delete and merge rows incorrectly, since the date does not uniquely identify a single row.
    • D. This is incorrect because appending reprocessed rows alongside the originals leaves the bad data in place, producing duplicate and conflicting rows for the corrected dates.

    Subdomain 1.5: Creating a logical flow of models and building clean DAGs

    16.A `fct_events` incremental model ingests roughly two billion rows a day, partitioned by `event_date`. Late-arriving events for the current day sometimes land after that day's partition has already run, and the warehouse bills by data scanned, so the chosen strategy must only touch the affected date partitions rather than scan the whole table. Which incremental strategy fits this table best?

    1. A.`insert_overwrite`, which replaces entire matching partitions with new data, so a late-arriving batch for `event_date` only rewrites that day's partition.
    2. B.`append`, which inserts every new row without checking for duplicates, so a reprocessed partition ends up with duplicate rows alongside the original ones.
    3. C.`merge`, which matches incoming rows against the full destination table on `unique_key` before updating or inserting, so late-arriving events for any `event_date` are captured correctly.
    4. D.`delete+insert`, which deletes rows matching `unique_key` before inserting new ones, and is well suited here because late-arriving `event_date` rows may not carry a reliably unique key.
    Show answer & explanation

    Correct answer: A — `insert_overwrite`, which replaces entire matching partitions with new data, so a late-arriving batch for `event_date` only rewrites that day's partition.

    • A. Insert_overwrite is designed for partitioned, time-series tables: it rebuilds only the partitions present in the new data, so a late-arriving day for `event_date` costs one partition rewrite instead of a full-table operation.
    • B. Append never checks for existing rows, so reprocessing a day to catch late arrivals would duplicate every row already loaded for that partition, which breaks correctness rather than solving the late-arrival problem.
    • C. Merge is a valid update mechanism but scans the destination table against `unique_key` to decide what to update, so its cost scales with the whole table rather than being scoped to the changed partitions, which is the opposite of what a two-billion-row table needs.
    • D. Delete+insert is aimed at situations where `unique_key` cannot be trusted to be unique; it does not inherently scope its work to a date partition, so it does not solve the cost-control goal in this scenario.

    Domain 2: Managing dbt models governance

    Subdomain 2.1: Adding contracts to models to ensure the shape of models

    17.Which constraint type does dbt's model contract feature actually enforce at the database engine level on both Postgres- and BigQuery-backed contracted tables?

    1. A.not_null
    2. B.primary_key
    3. C.foreign_key
    4. D.check
    Show answer & explanation

    Correct answer: A — not_null

    • A. Correct. `not_null` is one of the few constraint types that both Postgres and BigQuery actually enforce at the engine level on a contracted table, rejecting a null value rather than just documenting the intent.
    • B. Incorrect. BigQuery accepts `primary_key` as a definable constraint that appears in the DDL, but it does not reject duplicate values, so uniqueness is not actually enforced there.
    • C. Incorrect. `foreign_key` can be declared and rendered into the contract's DDL on BigQuery, but the referenced relationship is not validated, so orphaned keys can still load.
    • D. Incorrect. `check` constraints are not a generally enforced constraint type across these platforms and are unsupported or unenforced depending on the adapter, unlike `not_null`.

    Subdomain 2.3: Defining constraints in YAML to enforce data integrity at the platform level

    18.A team defines `primary_key`, `foreign_key`, `unique`, and `not_null` constraints on a contracted model that materializes on Snowflake. Which statements accurately describe what happens when this model is built?(Select 3)

    1. A.The `not_null` constraint is enforced by Snowflake at build time, so a row containing a null in that column aborts the build.
    2. B.The `primary_key` constraint is recorded as table metadata, but Snowflake does not reject a build that produces duplicate key values.
    3. C.The `foreign_key` constraint causes dbt to run a separate referential-integrity query against the warehouse before the model compiles.
    4. D.The `unique` constraint is enforced identically to Postgres, so Snowflake rejects the build the moment a duplicate value appears.
    5. E.All four constraint types are silently dropped from the compiled DDL, because Snowflake does not recognize any YAML-defined constraints.
    6. F.Declaring these four constraints still requires every column in the model to have both a `name` and a `data_type` set in the YAML.
    Show answer & explanation

    Correct answers: A, B, F — The `not_null` constraint is enforced by Snowflake at build time, so a row containing a null in that column aborts the build.; The `primary_key` constraint is recorded as table metadata, but Snowflake does not reject a build that produces duplicate key values.; Declaring these four constraints still requires every column in the model to have both a `name` and a `data_type` set in the YAML.

    • A. This is correct because not_null is one of the few constraint types Snowflake both defines and enforces at build time, so a null value in that column will cause the build to fail.
    • B. This is correct because Snowflake records primary_key as metadata on the table for documentation and downstream tooling, but it does not run a uniqueness check that would block the build.
    • C. This is incorrect because dbt does not issue a separate referential-integrity query for foreign_key on Snowflake; the constraint is written into the compiled DDL as metadata and nothing more happens at build time.
    • D. This is incorrect because Snowflake's unique constraint, like primary_key and foreign_key, is metadata only on this platform and does not reject a build containing duplicate values.
    • E. This is incorrect because Snowflake does record definable constraint types like not_null, primary_key, foreign_key, and unique in the compiled DDL; they are not silently discarded, even when only some of them are actively enforced.
    • F. This is correct because an enforced contract requires a full column declaration regardless of platform, so every column needs a name and data_type before dbt will compile the model at all.

    Subdomain 2.2: Creating different versions of our models and deprecating the old ones

    19.`dim_customers` declares versions `v: 1`, `v: 2`, and `v: 3`, with `latest_version: 2`. Which `ref()` calls resolve to the relation built from `dim_customers_v3.sql`?(Select 2)

    1. A.`ref('dim_customers')`
    2. B.`ref('dim_customers', v=3)`
    3. C.`ref('dim_customers', v='3')`
    4. D.`ref('dim_customers', v=2)`
    5. E.`ref('dim_customers_v3')`
    Show answer & explanation

    Correct answers: B, C — `ref('dim_customers', v=3)`; `ref('dim_customers', v='3')`

    • A. Without a version argument, this resolves to whichever version latest_version points at, which is version 2 here, not version 3.
    • B. Passing the version number directly pins the reference to version 3 regardless of which version is currently marked latest.
    • C. dbt accepts the version identifier as a string as well as an integer, so this also pins the reference to version 3.
    • D. This explicitly pins the reference to version 2, the version currently marked latest, not version 3.
    • E. ref() resolves against the declared model name plus an optional version argument, not against the underlying file name, so this is not a valid way to target a specific version.

    Domain 3: Debugging data modeling errors

    Subdomain 3.1: Understanding logged error messages

    20.A custom macro needs to drop and recreate a table only when a model is invoked with `dbt run --full-refresh`, and otherwise behave as a normal incremental append. Which Jinja condition inside the macro correctly detects that case?

    1. A.`{% if flags.FULL_REFRESH %}`, since this flag evaluates to `True` only when the `--full-refresh` CLI flag was passed to the current invocation.
    2. B.`{% if flags.WHICH == 'full-refresh' %}`, since `flags.WHICH` reports the exact CLI flag combination, including `--full-refresh`, used to invoke the current command.
    3. C.`{% if target.full_refresh %}`, since refresh behavior is a property of the active connection profile rather than a flag passed on the command line.
    4. D.`{% if var('full_refresh', false) %}`, since command-line flags are not exposed to Jinja directly and must instead be forwarded as a `--vars` dictionary entry.
    Show answer & explanation

    Correct answer: A — `{% if flags.FULL_REFRESH %}`, since this flag evaluates to `True` only when the `--full-refresh` CLI flag was passed to the current invocation.

    • A. This is the documented mechanism: `flags.FULL_REFRESH` is exposed to Jinja specifically to let macros branch on whether the current invocation passed `--full-refresh`, which is exactly the condition this macro needs to check.
    • B. This is incorrect because `flags.WHICH` reports the current dbt subcommand being run, such as `run`, `test`, or `compile`, not the combination of flags passed alongside it. `--full-refresh` would never appear as a value of `flags.WHICH`.
    • C. This is incorrect because the `target` object describes the active connection profile, such as warehouse, schema, and credentials, and has no `full_refresh` property. Full-refresh behavior is controlled by a CLI flag, not by profile configuration.
    • D. This is incorrect because dbt does expose CLI flags directly to Jinja through the `flags` object, so routing this through `var()` and a manually passed `--vars` entry is an unnecessary workaround that duplicates functionality dbt already provides natively.

    Subdomain 3.3: Troubleshooting .yml compilation errors

    21.A teammate's PR fails CI with a `Compilation Error` pointing to `models/marts/finance/schema.yml`, line 12, but the terminal snippet in the CI log is truncated. What is the most direct next step to see the full parsing context for that error?

    1. A.Delete the `target/` directory and rerun `dbt run --full-refresh` so dbt regenerates compiled SQL for every model in the project.
    2. B.Comment out the entire `schema.yml` file temporarily so the build passes, then re-add its properties one section at a time in a follow-up PR.
    3. C.Open `logs/dbt.log` from the CI run's build artifact and search for the same file path to see the full parsing context around line 12.
    4. D.Add a `data_tests: []` override in `dbt_project.yml` for the finance folder so property validation is skipped for that directory only.
    Show answer & explanation

    Correct answer: C — Open `logs/dbt.log` from the CI run's build artifact and search for the same file path to see the full parsing context around line 12.

    • A. The error occurs during parsing, before any SQL is compiled or executed, so clearing `target/` and forcing a full refresh does not surface more detail and wastes a warehouse-touching run on a problem that is purely YAML-level.
    • B. Disabling the whole file hides the failure instead of diagnosing it, and it also removes test and documentation coverage for every model described in that file while the real cause remains unknown.
    • C. `logs/dbt.log` records the full debug-level parsing event stream, including the untruncated error context around a specific file and line, making it the fastest way to see what the CI terminal cut off.
    • D. There is no per-directory switch to skip YAML property validation, and this option does not exist for `dbt_project.yml`'s `models:` config; even if it did, it would suppress tests rather than reveal the parsing error's cause.

    Subdomain 3.4: Developing and implementing a fix and testing it prior to merging

    22.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)

    1. A.Compare the rendered SQL in `target/compiled/` against the executed SQL in `target/run/` to see exactly what dbt substituted for `ref()` and `source()` calls.
    2. B.Tail `logs/dbt.log` for the run to see the exact SQL dbt sent to the warehouse, plus any Jinja rendering errors logged just before it.
    3. C.Delete the model file entirely and recreate it from a working model as a shortcut, hoping the rewrite avoids whatever is causing the rendering issue.
    4. D.Run `dbt docs generate` to rebuild the documentation catalog, assuming a stale catalog entry is the reason the columns look wrong at runtime.
    5. E.Isolate the model with `dbt run --select fct_revenue` on its own so upstream failures elsewhere in the DAG don't contaminate the compiled output you inspect.
    6. F.Increase the warehouse's query timeout setting so the statement has more time to finish compiling before dbt reports the run as failed.
    Show answer & explanation

    Correct answers: A, B, E — Compare the rendered SQL in `target/compiled/` against the executed SQL in `target/run/` to see exactly what dbt substituted for `ref()` and `source()` calls.; Tail `logs/dbt.log` for the run to see the exact SQL dbt sent to the warehouse, plus any Jinja rendering errors logged just before it.; Isolate the model with `dbt run --select fct_revenue` on its own so upstream failures elsewhere in the DAG don't contaminate the compiled output you inspect.

    • A. The compiled file shows what dbt rendered before execution, while the run file shows what actually reached the warehouse; diffing the two isolates whether the ref/source substitution itself is the source of the broken SQL.
    • B. The debug log captures both the final SQL dbt issued and any Jinja rendering events that preceded it, so scanning it can surface a rendering problem the compiled file alone doesn't explain.
    • C. Recreating the file from scratch is a guess that discards the diagnostic evidence you already have and does not identify whether the failure is a rendering issue or a plain SQL bug.
    • D. The documentation catalog reflects column metadata for the docs site; it plays no role in how a model is compiled or executed, so regenerating it cannot reveal a rendering bug.
    • E. Running the model on its own removes noise from unrelated models in the same invocation, giving compiled and run output you can trust as specific to this model's rendering behavior.
    • F. A query timeout controls how long the warehouse waits before aborting a statement; it does not affect whether dbt correctly substitutes `ref()` and `source()` calls during compilation.

    Subdomain 3.4: Developing and implementing a fix and testing it prior to merging

    23.Your CI job stores the `manifest.json` from the last successful `main` build in a dedicated `prod-state/` directory before each pull request build. On this PR you fixed a bug in `int_sessions` and want CI to test exactly the fixed model and everything downstream of it, without comparing against a manifest the current run just overwrote. Which practices are correct for this pre-merge test run? (Select all that apply)(Select 2)

    1. A.Point `--state` at the separate `prod-state/` directory instead of the run's own `target/` path, so the comparison manifest isn't overwritten.
    2. B.Select with `dbt build --select state:modified+`, since the trailing `+` extends the modified-node selection to every model descending from it in the DAG.
    3. C.Point `--state` directly at `target/`, so dbt compares the PR branch against the exact manifest this same invocation just generated moments earlier.
    4. D.Skip `--defer` entirely, since deferring unselected nodes to production would let the CI run reference tables the PR branch never rebuilt.
    5. E.Rerun the entire project with a plain `dbt build` and no selector, since state-based selection cannot be trusted for a model with modified tests.
    Show answer & explanation

    Correct answers: A, B — Point `--state` at the separate `prod-state/` directory instead of the run's own `target/` path, so the comparison manifest isn't overwritten.; Select with `dbt build --select state:modified+`, since the trailing `+` extends the modified-node selection to every model descending from it in the DAG.

    • A. Keeping the comparison manifest in a directory separate from the current run's `target/` output avoids the documented pitfall where dbt overwrites the manifest before it can be used for change detection.
    • B. The trailing `+` on `state:modified` extends the selection from the modified node to every descendant, matching exactly the scope needed to verify the fix and its blast radius before merging.
    • C. Using the same path for `--state` and the run's own target directory lets dbt overwrite the comparison manifest during parsing, so the run ends up comparing the branch against itself instead of against `main`.
    • D. Combining `state:modified+` with `--defer` is the documented slim-CI pattern: unselected nodes resolve against production tables that already exist, so skipping `--defer` here removes a correct, recommended practice rather than fixing a real problem.
    • E. Rebuilding the entire project discards the point of state-based selection and is unnecessary; `state:modified` reliably detects code and config changes, including changes to a model's tests.

    Subdomain 3.5: Managing dbt behavior with flags

    24.Which of the following statements about behavior-change flags in `dbt_project.yml` are correct? (Select all that apply.)(Select 3)

    1. A.They can only be declared under the `flags:` key in `dbt_project.yml`; dbt does not accept them as CLI options or environment variables.
    2. B.Checking a behavior-change flag into `dbt_project.yml` means every contributor who runs the project inherits the same setting automatically.
    3. C.Removing a behavior-change flag from `dbt_project.yml` reverts the project to dbt's current default for that flag, not to the old legacy behavior.
    4. D.A behavior-change flag set in `dbt_project.yml` can still be overridden for one invocation by passing the equivalent option on the CLI.
    5. E.Behavior-change flags can be toggled per environment using dbt Cloud environment variables, unlike other global configuration flags.
    6. F.Setting a behavior-change flag always changes SQL compiled for every model in the project, regardless of which models the flag actually affects.
    Show answer & explanation

    Correct answers: A, B, C — They can only be declared under the `flags:` key in `dbt_project.yml`; dbt does not accept them as CLI options or environment variables.; Checking a behavior-change flag into `dbt_project.yml` means every contributor who runs the project inherits the same setting automatically.; Removing a behavior-change flag from `dbt_project.yml` reverts the project to dbt's current default for that flag, not to the old legacy behavior.

    • A. Behavior-change flags are restricted to the `flags:` block of `dbt_project.yml` specifically so the setting stays version-controlled rather than being adjustable from the CLI or the shell environment.
    • B. Because the flag lives in a file that is committed to the repository, anyone who checks out the project and runs dbt picks up the same configured value without extra setup.
    • C. Once the flag entry is deleted, dbt falls through to whatever the current default is for that flag in the installed dbt version, which is the new behavior the flag was migrating toward, not the original legacy behavior.
    • D. This is incorrect: behavior-change flags are not accepted as CLI options at all, so there is no equivalent flag to pass, and the `dbt_project.yml` value cannot be overridden for a single run.
    • E. This is incorrect: behavior-change flags are not readable from environment variables, so a dbt Cloud environment variable has no effect on them; only the project file setting applies.
    • F. This is incorrect: each behavior-change flag documents the specific area of compiled SQL or resolution logic it affects, and unrelated models are compiled exactly as before the flag was set.

    Subdomain 3.2: Troubleshooting using compiled code

    25.During `dbt run`, dbt reports: `Found a cycle: model.jaffle_shop.customers --> model.jaffle_shop.stg_customers --> model.jaffle_shop.customers`. Which fix correctly resolves this specific error category?

    1. A.Remove the `ref('customers')` call mistakenly added inside `stg_customers.sql`, since a staging model should never depend on the mart built on top of it.
    2. B.Add `depends_on: {{ ref('stg_customers') }}` to the config block of `customers.sql` so dbt can resolve build order explicitly instead of inferring it.
    3. C.Change the materialization of `stg_customers` to `ephemeral` so it gets inlined into `customers.sql` at compile time instead of being built separately.
    4. D.Rerun with `dbt run --full-refresh` so dbt rebuilds the manifest from scratch and clears the stale cyclic edge between `customers` and `stg_customers` left over from a prior run.
    Show answer & explanation

    Correct answer: A — Remove the `ref('customers')` call mistakenly added inside `stg_customers.sql`, since a staging model should never depend on the mart built on top of it.

    • A. Correct: the reported cycle shows `stg_customers` referencing `customers`, which reverses the intended staging-to-mart dependency direction; removing that back-reference from `stg_customers.sql` breaks the loop and restores a valid acyclic graph.
    • B. Incorrect: `depends_on` is not a real dbt config key for declaring dependencies, and dbt already infers build order purely from `ref()`/`source()` calls, so adding this does not address the actual circular reference.
    • C. Incorrect: making `stg_customers` ephemeral changes how it is materialized, not which models it references; the cyclic `ref()` call would still exist and dbt would still detect the same cycle during graph validation.
    • D. Incorrect: a dependency cycle is a structural property of the `ref()` calls in the model files, not a caching artifact from a previous run, so `--full-refresh` rebuilds data but does not remove or fix a circular reference.

    Domain 4: Troubleshooting and optimizing dbt pipelines

    Subdomain 4.1: Troubleshooting and managing failure points in the DAG

    26.A teammate pastes this end of a `dbt build` log and asks why three models show `SKIP` instead of running: ``` 16:42:03 1 of 7 START sql table model staging.stg_customers ........... [RUN] 16:42:04 1 of 7 ERROR creating sql table model staging.stg_customers ... [ERROR in 0.61s] 16:42:04 2 of 7 SKIP relation intermediate.int_customer_orders ......... [SKIP] 16:42:04 3 of 7 SKIP relation marts.fct_customers .................... [SKIP] 16:42:04 4 of 7 SKIP relation marts.dim_customers .................... [SKIP] ``` What is the most accurate explanation for the three `SKIP` lines?

    1. A.`int_customer_orders`, `fct_customers`, and `dim_customers` all depend on `stg_customers` through `ref()`, so dbt automatically skips any node downstream of a node that errored.
    2. B.dbt applies a global `--fail-fast` default that halts the entire invocation the moment any single model errors, marking every remaining node in the run as skipped.
    3. C.The three skipped models share a Jinja macro with `stg_customers`, and a macro compilation failure in one model always cascades to every model that imports that macro.
    4. D.dbt Cloud's job scheduler detected the earlier database error and proactively cancelled the remaining steps in the run to avoid consuming extra warehouse credits.
    Show answer & explanation

    Correct answer: A — `int_customer_orders`, `fct_customers`, and `dim_customers` all depend on `stg_customers` through `ref()`, so dbt automatically skips any node downstream of a node that errored.

    • A. dbt builds nodes in dependency order and, when a node errors, marks every node reachable downstream of it as skipped rather than attempting to build on top of a table that was never created.
    • B. `--fail-fast` is an opt-in flag, not a default, and even without it dbt still skips only the descendants of the failed node rather than every remaining node in the entire invocation.
    • C. The log shows a Database Error during table creation, not a Jinja compilation failure, and shared macro usage alone does not create a `ref()` dependency edge that would cause automatic skipping.
    • D. This log is from a local `dbt build` invocation with no scheduler involved; the skip behavior comes from dbt's own dependency-aware execution engine, not an external job orchestrator cancelling steps.

    Subdomain 4.2: Using dbt clone

    27.A Snowflake-backed project's Slim CI job takes 40 minutes because a 200 GB incremental model with `on_schema_change: fail` runs a full refresh on every pull request, since the model does not yet exist in the PR-specific schema. The team wants CI to exercise the same incremental path that runs in production without rebuilding the full history each time. Which action addresses this while keeping the incremental behavior test accurate?

    1. A.Run `dbt clone --select state:modified+,config.materialized:incremental,state:old` ahead of the build step, so the pre-existing relation lands in the PR schema and `is_incremental()` evaluates true.
    2. B.Set `full_refresh: false` in the model's config block, so dbt treats the relation as already present in the PR schema and compiles the incremental merge logic regardless of what actually exists there.
    3. C.Add `--full-refresh` to the nightly production job, so the incremental model rebuilds from scratch before each pull request branches off, giving CI a clean baseline relation.
    4. D.Switch the model to `table` materialization for the CI target only, since a full table rebuild there costs about the same compute as an incremental merge on this dataset.
    Show answer & explanation

    Correct answer: A — Run `dbt clone --select state:modified+,config.materialized:incremental,state:old` ahead of the build step, so the pre-existing relation lands in the PR schema and `is_incremental()` evaluates true.

    • A. Cloning the pre-existing incremental relation into the PR schema before the build step means `is_incremental()` sees an existing target and compiles the merge logic instead of a full rebuild, so CI exercises the same code path that runs in production without copying the full history.
    • B. The `full_refresh` config controls whether a model is force-rebuilt; it does not create a relation where none exists, so the PR schema would still be missing the target table and `is_incremental()` would still evaluate to false.
    • C. Rebuilding the production job nightly does nothing for the PR-specific schema dbt CI builds into — that schema still starts empty for every pull request, so the incremental model still full-refreshes there.
    • D. A full `table` rebuild for CI defeats the goal of testing the incremental path at all, and on a 200 GB model it does not meaningfully reduce cost compared to the full refresh the team is trying to avoid.

    Domain 5: Implementing dbt tests

    Subdomain 5.1: Using generic, singular, custom, custom generic, and unit tests on a wide variety of models and sources

    28.The `orders` model has a `status` column that must only ever contain `'placed'`, `'shipped'`, `'completed'`, or `'returned'`. During large backfills, the team wants violations of this check to warn rather than block CI, and they want to limit how many rows the test scans to control warehouse spend. Based on documented `data_tests` config options, which changes achieve this? (Select all that apply.)(Select 3)

    1. A.Set `config: severity: warn` on the `accepted_values` test so a violation produces a warning instead of failing the CI run.
    2. B.Set `config: limit: <n>` on the test to cap how many failing rows dbt returns and processes during a large-scale evaluation run.
    3. C.Set `arguments: quote: false` on the test so string comparisons against the accepted values list skip quoting overhead during large backfills.
    4. D.Set `config: where: "created_at >= dateadd(day, -1, current_date)"` to scope the test to recent rows during a backfill window and reduce rows scanned.
    5. E.Set `config: store_failures_as: view` so failing rows are cheaper to persist, which by itself lowers the severity of the test from error to warn.
    6. F.Remove the `accepted_values` test from the column and replace it with a `not_null` test, which is documented as a lower-cost check for large tables.
    Show answer & explanation

    Correct answers: A, B, D — Set `config: severity: warn` on the `accepted_values` test so a violation produces a warning instead of failing the CI run.; Set `config: limit: <n>` on the test to cap how many failing rows dbt returns and processes during a large-scale evaluation run.; Set `config: where: "created_at >= dateadd(day, -1, current_date)"` to scope the test to recent rows during a backfill window and reduce rows scanned.

    • A. severity is a documented config key on data tests, and setting it to warn is precisely how a failing check is turned into a warning instead of a pipeline-blocking error.
    • B. limit is a documented config key that caps how many failing rows a test returns, which directly reduces the scan cost of a large-table check during a backfill.
    • C. There is no documented quote argument for accepted_values or any other generic test; string quoting is handled internally by the compiled SQL, not by a user-facing config.
    • D. where is a documented config key that filters which rows a test evaluates, so scoping it to recent rows is a valid way to shrink the scanned dataset during a backfill window.
    • E. store_failures_as only controls the materialization of the table dbt writes failing rows to; it has no effect on the severity setting that determines whether a failure warns or errors.
    • F. Swapping in a not_null test checks an entirely different condition and would no longer catch invalid status values at all, so it does not satisfy either the warn or the cost-control requirement.

    Subdomain 5.2: Testing assumptions for dbt models and sources

    29.To run only the data tests defined on the `orders` table within the `jaffle_shop` source, without running tests on any other source table, an analytics engineer should run `dbt test --select ___`.

    1. A.source:jaffle_shop.orders
    2. B.source:jaffle_shop
    3. C.test_type:source
    Show answer & explanation

    Correct answer: A — source:jaffle_shop.orders

    • A. The `source:<source_name>.<table_name>` selector scopes node selection down to tests defined on that one specific source table, which matches the requirement to run only the `orders` table's tests.
    • B. `source:jaffle_shop` without a table qualifier selects tests across every table in the `jaffle_shop` source, which is broader than the single-table scope the requirement asks for.
    • C. There is no `test_type:source` selector in dbt's node selection syntax; test type selectors distinguish generic from singular or unit tests, not which resource a test belongs to.

    Subdomain 5.3: Implementing various testing steps in the workflow

    30.A `not_null` test on `stg_events.session_id` fails for a small, accepted percentage of rows that the data team already knows about, but they still want every CI run to surface how many rows are affected instead of silently ignoring the column. Which two changes together achieve this?(Select 2)

    1. A.Set `config: severity: warn` on the test so the run reports the failure without blocking the pipeline.
    2. B.Add `config: store_failures: true` so the failing rows land in a queryable audit table each run.
    3. C.Delete the `not_null` test entirely, since a column with expected nulls makes the check meaningless going forward.
    4. D.Replace `not_null` with `accepted_values` listing every non-null `session_id` seen so far, so unexpected values still fail loudly.
    5. E.Raise the test's `error_if` threshold to a very high row count so the run stops failing regardless of how many rows are affected.
    Show answer & explanation

    Correct answers: A, B — Set `config: severity: warn` on the test so the run reports the failure without blocking the pipeline.; Add `config: store_failures: true` so the failing rows land in a queryable audit table each run.

    • A. Setting the test severity to warn keeps the row count visible in the run output while letting the pipeline continue past the known, accepted nulls.
    • B. Storing failures writes the offending rows to a `dbt_test__audit` table each run, giving the team a queryable record of exactly which rows failed without stopping the build.
    • C. Removing the test entirely stops both the failure and the row-count visibility the team explicitly wants to keep, so it does not satisfy the requirement.
    • D. Enumerating every observed value in an `accepted_values` list does not test for nulls at all, and it requires maintaining a growing list by hand instead of reporting the null count.
    • E. Raising the failure threshold to an unrealistic number stops the test from failing but also hides a genuine spike in nulls, defeating the goal of keeping the issue visible.

    Domain 6: Implementing and maintaining external dependencies

    Subdomain 6.1: Implementing dbt exposures

    31.Before signing off on a new finance dashboard exposure, an engineer double-checks how dbt actually represents and builds it. Which of the following statements are true about how dbt treats an exposure?(Select 3)

    1. A.The exposure is rendered as a distinct node in the dbt DAG rather than being merged into an existing model node.
    2. B.The exposure can be targeted with graph operators, such as `+exposure:name`, to select everything feeding into it.
    3. C.dbt materializes the exposure as a view in the target schema so BI tools can query it directly like any other model.
    4. D.The exposure's `name` must be unique within the project and can only contain letters, numbers, and underscores.
    5. E.dbt requires a corresponding `.sql` file defining exposure logic, mirroring how models are compiled and executed.
    6. F.dbt automatically creates a test asserting the exposure's dependencies never fail, without any configuration.
    Show answer & explanation

    Correct answers: A, B, D — The exposure is rendered as a distinct node in the dbt DAG rather than being merged into an existing model node.; The exposure can be targeted with graph operators, such as `+exposure:name`, to select everything feeding into it.; The exposure's `name` must be unique within the project and can only contain letters, numbers, and underscores.

    • A. Correct. dbt gives every exposure its own node in the manifest and DAG, shown as a distinct orange indicator, rather than folding it into the model it depends on.
    • B. Correct. Graph operators work against exposure nodes the same way they work against models, so `+exposure:name` selects the exposure together with everything upstream of it.
    • C. Incorrect. An exposure is metadata describing a downstream consumer; dbt never creates a table or view for it, so there is nothing in the target schema for a BI tool to query.
    • D. Correct. Exposure names follow the same snake_case identifier rules as other resources, restricted to letters, numbers, and underscores, and must be unique in the project.
    • E. Incorrect. Exposures are defined entirely in YAML properties files; they have no compiled SQL and are not executed like a model.
    • F. Incorrect. dbt does not generate any tests automatically for an exposure or its dependencies; any testing must be configured explicitly on the underlying models.

    Subdomain 6.2: Implementing source freshness

    32.During a `dbt source freshness` run, the `orders` table reports state `error` while the `customers` table reports state `pass`. Which statements accurately describe how dbt determines and records these results? (Select all that apply)(Select 3)

    1. A.dbt compares the query's `calculated_at` timestamp against the maximum `loaded_at_field` value, then classifies the gap against `warn_after` and `error_after`.
    2. B.The `error` state on `orders` causes `dbt source freshness` to exit with a nonzero code, which a CI job can use to fail that pipeline step and stop downstream runs.
    3. C.Because `customers` passed, its row is left out of `target/sources.json` entirely, since dbt only records tables that triggered a warning or an error state.
    4. D.A given table's freshness state can only ever resolve to `pass` or `error`; a `warn` classification is reserved for the source-level config block and never applies to an individual table.
    5. E.Both `orders` and `customers` are recorded in `target/sources.json`, each with its own `max_loaded_at`, `snapshotted_at`, and `state` values regardless of the outcome.
    6. F.dbt only evaluates `error_after` during scheduled production runs, while `warn_after` is evaluated exclusively when the command is invoked with an explicit `--select` flag.
    Show answer & explanation

    Correct answers: A, B, E — dbt compares the query's `calculated_at` timestamp against the maximum `loaded_at_field` value, then classifies the gap against `warn_after` and `error_after`.; The `error` state on `orders` causes `dbt source freshness` to exit with a nonzero code, which a CI job can use to fail that pipeline step and stop downstream runs.; Both `orders` and `customers` are recorded in `target/sources.json`, each with its own `max_loaded_at`, `snapshotted_at`, and `state` values regardless of the outcome.

    • A. This is correct: dbt calculates elapsed time as the gap between the snapshot timestamp and the maximum loaded-at value, then checks that gap against the warn and error thresholds to assign a state. That comparison is the core mechanism behind every freshness result.
    • B. This is correct: an error state produces a nonzero process exit code, which is exactly the signal orchestration tools use to mark a job step as failed. That is what lets a CI pipeline stop before running expensive downstream models against stale data.
    • C. This is incorrect: dbt writes an entry for every table it checks, whether the result is pass, warn, or error, so passing tables are not dropped from the output. Omitting passing tables would make the artifact useless for auditing overall freshness.
    • D. This is incorrect: `warn` is a valid per-table state alongside `pass` and `error`, produced whenever elapsed time exceeds `warn_after` but not yet `error_after`. It is not restricted to source-level configuration.
    • E. This is correct: `target/sources.json` includes a record for every table dbt checked, each carrying its own timestamps and state, independent of whether that particular table passed or failed. This is what makes the file a complete freshness report.
    • F. This is incorrect: both thresholds are evaluated on every invocation of the command regardless of environment or flags used, and `--select` only narrows which sources are checked, not which threshold type applies.

    Domain 7: Leveraging the dbt state

    Subdomain 7.1: Understanding state and state selection

    33.Your CI pipeline runs the following commands in order: `dbt build --select state:modified+ --state ./target --target-path ./target`, where `./target` holds both the previous deploy's manifest and the current run's compiled artifacts. On the next CI run, `state:modified+` silently returns no results even though model changes were merged in between. What is the most likely cause, and what should you change?

    1. A.The current run's own parsing overwrote `./target/manifest.json` before comparison ran, so `--state` should point at a manifest stored in a separate `./state` folder, decoupled from `--target-path`.
    2. B.The selector is missing a leading `+`, so upstream parent models are never compared; the expression needs to read `+state:modified+` to catch changes originating further up the DAG.
    3. C.`dbt build` cannot accept `--target-path` together with `--state` in the same invocation, so the pipeline must drop `--target-path` and let both artifacts share the default `target/` directory.
    4. D.The comparison manifest is stale because `state:modified` reads change status from `run_results.json`, so the pipeline needs a `dbt retry` step before `dbt build` to refresh that file.
    Show answer & explanation

    Correct answer: A — The current run's own parsing overwrote `./target/manifest.json` before comparison ran, so `--state` should point at a manifest stored in a separate `./state` folder, decoupled from `--target-path`.

    • A. Pointing `--state` and `--target-path` at the same directory means the parse step for the current run writes a fresh manifest.json into that folder before the comparison happens, so dbt ends up comparing the project against itself. Storing the prior manifest in a dedicated path like `./state`, separate from where the current run writes its own artifacts, removes that overwrite risk.
    • B. A leading `+` extends selection upstream to ancestors, which is unrelated to why the comparison returns nothing; the problem here is that the comparison manifest itself is no longer the prior deploy's manifest. Adding an upstream operator would not restore a correct baseline to compare against.
    • C. `dbt build` accepts `--target-path` and `--state` together without conflict; they are independent flags controlling where compiled output goes and what manifest to diff against, respectively. Removing `--target-path` would not address the real issue, which is that both flags happen to resolve to the same directory.
    • D. `state:modified` compares the manifest, not run_results.json — that file backs `result:` selectors and `dbt retry`, not `state:modified`. Running `dbt retry` first would not refresh or protect the comparison manifest and would not fix the described symptom.

    Subdomain 7.2: Using dbt retry

    34.A merge job runs the following two steps: ``` dbt build dbt retry --target-path retry_target ``` The `dbt build` step fails partway through with one model in error. The `dbt retry` step then reports that there is nothing to do instead of resuming the failed model. Which line is responsible for that unexpected result?

    1. A.The `--target-path retry_target` override, since retry looks under that directory for run_results.json, which the new path doesn't contain.
    2. B.The `dbt build` line itself, because a completed build call permanently deletes run_results.json before any retry command can read it.
    3. C.The missing `--full-refresh` flag, since incremental models cannot produce a run_results.json entry without a full rebuild first.
    4. D.The missing `--select` flag on the build step, since dbt records node-level results only when an explicit selection criterion is passed to build.
    Show answer & explanation

    Correct answer: A — The `--target-path retry_target` override, since retry looks under that directory for run_results.json, which the new path doesn't contain.

    • A. Retry reads run_results.json from the target-path directory to find the prior failure; pointing the retry step at a fresh, empty target-path means it never finds the build step's failure record and treats the job as having nothing to resume.
    • B. A build invocation that fails partway through still writes run_results.json recording the failure; it does not delete the artifact, so this is not what causes the mismatch.
    • C. run_results.json is produced for any build invocation regardless of materialization, so the absence of a full-refresh flag on the build step is not what breaks the retry lookup.
    • D. dbt records node-level results for every build invocation, selected or not; an unscoped build still produces a run_results.json, so a missing select flag does not explain the failure.

    Subdomain 7.2: Using dbt retry

    35.A `dbt build` job fails partway through, leaving one model in error and its two downstream models skipped. After fixing the broken model's SQL, an engineer wants to resume the build without reprocessing the seeds and models that already finished successfully. They should run `dbt ___`.

    1. A.retry
    2. B.build --full-refresh
    3. C.run --select state:modified
    Show answer & explanation

    Correct answer: A — retry

    • A. This command resumes an invocation from the point of failure, rebuilding only the nodes that errored or were skipped, which matches the engineer's goal of not reprocessing what already succeeded.
    • B. A full-refresh build reprocesses the selected nodes from scratch and is not scoped to just the failure, so it would rebuild the seeds and models that already finished.
    • C. State-based selection compares the project against a saved manifest to find changed models; it does not target the specific nodes that failed in the last invocation.

    Want the full experience?

    These are just samples. Practice the full DBT Analytics Engineering question bank in quiz mode — free, no signup, with domain practice and exam simulation.