dbt Interview Questions and Answers Part 7 - Sources, Seeds and Configuration

dbt Interview Q&A · Part 7 of 10

In this post we are going to cover sources, seeds and configuration in dbt: when to use seeds, config precedence, tags and selectors, targets and schemas, strict YAML in dbt v2, and documentation. These are 7 real dbt interview questions for data engineers, asked the way they come up on a real project, and the last one is a scenario where you join a team whose dbt project has no conventions and you have to clean it up.

This is the part of dbt that decides where data comes from, how each model is built, and where it lands. Most of it lives in YAML: sources, seed settings, configs, tags, selectors and descriptions. Interviewers ask about it because the bugs here are quiet ones. A config that loses to another level, or a model in the wrong schema, doesn't throw an error. It just gives you the wrong result.

Part 7 of the Real Time dbt Interview Questions and Answers series on TechBrothersIT.

The short answer

Say this first

Seeds are for small, static reference data; raw data comes from your loader and is declared as a source. The most specific config wins (model file, then YAML, then dbt_project.yml), while tags add up. Jobs select with tags and named selectors, each target has its own schema, and dbt v2 fails the parse on unknown YAML keys.

Let's create the project and some sample data

Our project has raw payments, a small seed that maps payment method codes to names, a staging model that joins the two, and a finance model, fct_daily_revenue. The profiles.yml has two targets in one DuckDB file: dev writes to dbt_ali, and prod to analytics. Here are all the starting files.

dbt_project.yml
name: shop
version: '1.0'
profile: shop

models:
  shop:
    +materialized: view
    finance:
      +schema: finance
      +tags: ['finance']
profiles.yml
shop:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: warehouse.duckdb
      schema: dbt_ali
      threads: 4
    prod:
      type: duckdb
      path: warehouse.duckdb
      schema: analytics
      threads: 4
macros/load_raw.sql
{% macro load_raw() %}
  {# stands in for the loading tool: lands raw payments in the raw schema #}
  {% do run_query("create schema if not exists raw") %}
  {% do run_query("create or replace table raw.payments as select * from (values
      (1, 101, 'CC', 120.00, timestamp '2026-10-24 09:10:00'),
      (2, 102, 'PP', 45.50, timestamp '2026-10-24 11:40:00'),
      (3, 103, 'BT', 300.00, timestamp '2026-10-24 15:05:00'),
      (4, 104, 'DC', 80.25, timestamp '2026-10-25 08:30:00'),
      (5, 105, 'CC', 60.00, timestamp '2026-10-25 10:15:00'))
    as t(payment_id, order_id, method_code, amount, paid_at)") %}
{% endmacro %}
seeds/payment_methods.csv
code,method_name,is_card
CC,Credit card,true
DC,Debit card,true
PP,PayPal,false
BT,Bank transfer,false
seeds/_seeds.yml
seeds:
  - name: payment_methods
    description: Payment method codes. Changes a few times a year.
    config:
      column_types:
        code: varchar(2)
        method_name: varchar
        is_card: boolean
models/staging/_sources.yml
sources:
  - name: raw
    description: Landed by the loading tool, not by dbt.
    schema: raw
    tables:
      - name: payments
models/staging/stg_payments.sql
select
    p.payment_id,
    p.order_id,
    m.method_name,
    m.is_card,
    p.amount,
    p.paid_at
from {{ source('raw', 'payments') }} as p
join {{ ref('payment_methods') }} as m
    on m.code = p.method_code
models/staging/_staging.yml
models:
  - name: stg_payments
    description: One row per payment, with the method name.
    columns:
      - name: payment_id
        data_tests: [unique, not_null]
      - name: method_name
        data_tests: [not_null]
models/finance/fct_daily_revenue.sql
{{ config(materialized='view') }}  -- from debugging

select
    cast(paid_at as date) as payment_date,
    method_name,
    count(*) as payments,
    sum(amount) as revenue
from {{ ref('stg_payments') }}
group by 1, 2
models/finance/_finance.yml
models:
  - name: fct_daily_revenue
    config:
      materialized: table
      tags: ['nightly']

In a real project a loading tool lands the raw data. Here a small macro stands in for it, so first we run dbt run-operation load_raw.

Terminal
$ dbt run-operation load_raw
Finished 'run-operation' successfully for target 'dev'

The raw payments are in place, so now we can start with the questions.

Question 1: A colleague loads a ten-thousand-row CSV with dbt seed every night. When should you use seeds, and when not?

Seeds are for small, static reference data. The docs say they're best suited to static data that changes infrequently, like code mappings. A file that changes every night is raw data, so it should be a source.

SeedSource
Datasmall, static reference dataraw data from the loading tool
Changesa few times a year, in a pull requestevery load, outside dbt
In SQLref('payment_methods')source('raw', 'payments')

Our seed, payment_methods, maps payment method codes to names. A YAML file next to it describes the seed and sets the column types, so nothing depends on type inference. Codes with leading zeros are the classic victim. Let's run dbt seed.

Terminal
$ dbt seed
 Succeeded seed  dbt_ali.payment_methods (table)

Finished 'seed' successfully for target 'dev'
Processed: 1 seed
Summary: 1 total | 1 success

It lands as a table in the dev schema, dbt_ali.payment_methods. The file lives in Git, so every change is reviewed, and models use it with ref.

For the nightly file, land it with your loading tool, declare it as a source, and read it with source. That is what our staging model does: it reads raw payments with source('raw', 'payments'), and joins the seed with ref('payment_methods').

Pitfall

The mistake I see is seeds as a free loader: every run reloads the whole file, the history bloats Git, and nothing checks freshness.

Question 2: A config is set in dbt_project.yml, in a YAML file and in the model, and the model does not behave as expected. How does config precedence work?

The most specific config wins. A config block in the model file beats the properties YAML, and the YAML beats dbt_project.yml, where a subfolder beats the project level.

Let's look at our revenue model. dbt_project.yml makes every model a view. The YAML says this model is a table. But the model file still has an old config block from debugging that says view. We ask dbt what it resolved with dbt ls and JSON output.

Terminal
$ dbt ls -s fct_daily_revenue --output json --output-keys name materialized tags
=======================Selected nodes=======================
{"materialized":"view","name":"fct_daily_revenue","tags":["finance","nightly"]}

Finished 'list' successfully for target 'dev'

dbt says "materialized":"view". The model file won. The tags show finance from the folder and nightly from the YAML, so they don't replace each other. That is the other half of the rule:

  • Tags and hooks add up across levels
  • meta merges by top-level key
  • Most other configs are replaced

Now we delete the config block and run the same command.

models/finance/fct_daily_revenue.sql
select
    cast(paid_at as date) as payment_date,
    method_name,
    count(*) as payments,
    sum(amount) as revenue
from {{ ref('stg_payments') }}
group by 1, 2
Terminal
$ dbt ls -s fct_daily_revenue --output json --output-keys name materialized tags
=======================Selected nodes=======================
{"materialized":"table","name":"fct_daily_revenue","tags":["finance","nightly"]}

Finished 'list' successfully for target 'dev'

Now the YAML wins: "materialized":"table".

Real project tip

Give each config one home: folder defaults in dbt_project.yml, exceptions in YAML, and a config block only when it belongs with the SQL. dbt ls is the fastest way to check what dbt resolved.

Question 3: The nightly job should only run the finance models. How do you use tags and selectors?

Tag by folder in dbt_project.yml, then select on the tag, and give the job a named selector in selectors.yml.

First, the tags. Every model in the finance folder gets the finance tag from dbt_project.yml, so nobody has to remember it. You add model tags where you need them, like nightly. We saw both on fct_daily_revenue in the last question.

Let's add a second finance model, a marketing model, and a selector called nightly_finance. Then we run three list commands.

models/finance/fct_large_payments.sql
select payment_id, method_name, amount
from {{ ref('stg_payments') }}
where amount >= 100
models/marketing/fct_card_share.sql
select
    count(*) filter (where is_card) / count(*) as card_share
from {{ ref('stg_payments') }}
selectors.yml
selectors:
  - name: nightly_finance
    description: The nightly finance job
    definition:
      method: tag
      value: finance
      parents: true
Terminal
$ dbt ls -s tag:finance
=======================Selected nodes=======================
shop.finance.fct_daily_revenue
shop.finance.fct_large_payments

Finished 'list' successfully for target 'dev'
$ dbt ls -s "tag:finance,tag:nightly"
=======================Selected nodes=======================
shop.finance.fct_daily_revenue

Finished 'list' successfully for target 'dev'
$ dbt ls --selector nightly_finance
=======================Selected nodes=======================
shop.finance.fct_daily_revenue
shop.finance.fct_large_payments
shop.staging.stg_payments
shop.payment_methods
source:shop.raw.payments
shop.staging.not_null_stg_payments_method_name
shop.staging.not_null_stg_payments_payment_id
shop.staging.unique_stg_payments_payment_id

Finished 'list' successfully for target 'dev'

Here is what each one shows:

  • tag:finance lists both finance models, without the marketing model
  • A comma means intersection: tag:finance,tag:nightly gives only fct_daily_revenue. A space means union.
  • --selector nightly_finance takes the finance tag with parents, so the job also builds what the finance models need: the two finance models, stg_payments and its three tests, the payment_methods seed, and the raw source

For a job, don't paste long selections into the scheduler. The job is just dbt build --selector nightly_finance.

Interview tip

Use --select or -s. -m is the old flag: v1.10 and later warn about it, and the v2 upgrade guide says to replace it.

Question 4: Dev and prod builds write to the wrong schemas. How do you set up targets and environments?

A target is one connection in profiles.yml: the adapter, the database and the schema. Each environment gets its own target, and the same code builds into the right schema for each one.

Here, dev writes to a personal schema, dbt_ali, and prod to analytics. For the demo both share one DuckDB file. In a real warehouse, prod has its own database and service account. Let's build the revenue model and its parents in dev, then run the same command with --target prod.

Terminal
$ dbt build -s +fct_daily_revenue
 Succeeded seed  dbt_ali.payment_methods (table)
 Succeeded model dbt_ali.stg_payments (view)
    Passed test  not_null_stg_payments_method_name
    Passed test  not_null_stg_payments_payment_id
    Passed test  unique_stg_payments_payment_id
 Succeeded model dbt_ali_finance.fct_daily_revenue (table)

Finished 'build' successfully for target 'dev'
Processed: 2 models | 3 tests | 1 seed
Summary: 6 total | 6 success
$ dbt build -s +fct_daily_revenue --target prod
 Succeeded seed  analytics.payment_methods (table)
 Succeeded model analytics.stg_payments (view)
    Passed test  not_null_stg_payments_method_name
    Passed test  not_null_stg_payments_payment_id
    Passed test  unique_stg_payments_payment_id
 Succeeded model analytics_finance.fct_daily_revenue (table)

Finished 'build' successfully for target 'prod'
Processed: 2 models | 3 tests | 1 seed
Summary: 6 total | 6 success

In dev, the seed and staging go to dbt_ali, and the finance model to dbt_ali_finance. In prod, everything lands in analytics and analytics_finance. Same code: ref resolves to the right schema in each target.

That prefix is the usual wrong-schema surprise. By default, generate_schema_name puts the target schema in front of the custom schema, so dev never writes into prod's finance schema. If prod needs the bare name, override that macro, as in Part 6 of this series.

On a real project:

  • Dev is the default target
  • Prod credentials live only in the scheduler
  • Secrets come from env_var(), never from Git

Question 5: A model has a typo in a config key and dbt v1 silently ignored it, but dbt v2 now fails the parse. Why, and how do you fix it?

dbt v2 validates every YAML file against the dbt spec, so an unknown key is an error, not something it skips. The fix is to correct the key, and to put your own attributes under meta.

A teammate wanted the marketing model as a table, and typed materalized, without the first i. dbt v1 ignores the unknown key, with no warning on DuckDB, so the model quietly stays a view. On dbt v1.11 and later, Snowflake, Databricks, BigQuery and Redshift do warn. Either way the bug is a wrong result, not an error. Let's run dbt parse on dbt v2.

models/marketing/_marketing.yml
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      materalized: table
Terminal (expected error)
$ dbt parse
=================== Errors and Warnings ====================
[error] [UnusedConfigKey (dbt1060)]: While parsing config: Ignored unexpected key `"materalized"`. YAML
  path: `config.materalized`.
  --> models/marketing/_marketing.yml:5:7
suggestion: Run 'dbt deps' to see the latest fusion compatible packages. For compatibility errors, try
  the autofix script: https://github.com/dbt-labs/dbt-autofix

Finished 'parse' with 1 error for target 'dev'

It fails with UnusedConfigKey (dbt1060), and it names the key, config.materalized, and the file and line, models/marketing/_marketing.yml:5:7. We fix the key, add an owner under config: meta:, and run the model.

models/marketing/_marketing.yml
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      materialized: table
      meta:
        owner: marketing-analytics
Terminal
$ dbt run -s fct_card_share
 Succeeded model dbt_ali.fct_card_share (table)

Finished 'run' successfully for target 'dev'
Processed: 1 model
Summary: 1 total | 1 success

The model builds as a table: dbt_ali.fct_card_share (table). meta is the one place for custom keys.

Before an upgrade, find these on dbt v1:

  • On v1.12, dbt parse --use-v2-parser runs the new parser, in beta
  • The dbt-autofix tool fixes common deprecations, like properties that moved under config. Typos, you fix by hand.

So strict parsing is a feature. In v1, this bug reached production. In v2, it can't get past dbt parse in CI.

Question 6: You want to document models and columns once and show them to analysts. How do you use descriptions, docs blocks and dbt docs?

Descriptions live in the same YAML as the configs and tests: one for the model and one per column. For long text, or text you reuse, write a docs block once and reference it with doc().

Keep descriptions next to the model, so they change in the same pull request as the code. Our docs block lives in models/docs.md, and the model description in _finance.yml calls doc("fct_daily_revenue"). Let's check the rendered description with dbt ls, then build the site with dbt docs generate.

models/docs.md
{% docs fct_daily_revenue %}
Revenue per day and payment method, in US dollars.
Refunds are not subtracted.
{% enddocs %}
models/finance/_finance.yml
models:
  - name: fct_daily_revenue
    description: '{{ doc("fct_daily_revenue") }}'
    config:
      materialized: table
      tags: ['nightly']
    columns:
      - name: payment_date
        description: Day the payment was captured (UTC).
      - name: revenue
        description: Sum of amount, before refunds.
Terminal
$ dbt ls -s fct_daily_revenue --output json --output-keys name description
=======================Selected nodes=======================
{"description":"Revenue per day and payment method, in US dollars.\nRefunds are not
  subtracted.","name":"fct_daily_revenue"}

Finished 'list' successfully for target 'dev'
$ dbt docs generate
Running compile; pass `--no-compile` to export the existing index instead.
 Succeeded seed  dbt_ali.payment_methods (table)
 Succeeded model dbt_ali.stg_payments (view)
 Succeeded test  not_null_stg_payments_method_name (models/staging/_staging.yml:8:22)
 Succeeded test  not_null_stg_payments_payment_id (models/staging/_staging.yml:6:30)
 Succeeded test  unique_stg_payments_payment_id (models/staging/_staging.yml:6:22)
 Succeeded model dbt_ali.fct_card_share (table)
 Succeeded model dbt_ali_finance.fct_daily_revenue (table)
 Succeeded model dbt_ali_finance.fct_large_payments (view)
 Generated target (reading info_schema/v1/) — no column lineage; rerun with `--static-analysis strict`
   to include it
Host the contents of target on any static file server. Note that this publishes everything else in the
  target directory too, including compiled SQL and any stored test failures.

Finished 'docs' successfully for target 'dev'

dbt ls shows the docs block rendered into the model description: Revenue per day and payment method, in US dollars. and then Refunds are not subtracted. In dbt v2, dbt docs generate builds a static site in the target folder, and you can host it on any static file server. dbt docs serve opens it locally.

But read the next line of the output. Hosting the folder publishes everything in it, including compiled SQL and any stored test failures. And column lineage needs --static-analysis strict, in the full dbt distribution. On the dbt platform, analysts use Catalog.

Code review rule

No model gets merged without descriptions for the model and its key columns. Write the why, not the column name again.

Scenario 7: You join a team whose dbt project has no conventions: random names, configs everywhere, no docs. How do you clean it up?

Not with a big rewrite. Agree on the rules with the team, move one piece at a time, and let CI enforce it.

First, write the rules down:

  • Layer prefixes: stg_, int_, fct_, dim_
  • One folder per layer or domain
  • One home for each config
  • Build after each move
  • Enforce in CI
  • One folder per pull request

Then we move the configs to folder level in dbt_project.yml: staging as views, finance and marketing as tables, and a schema and a tag per domain. The YAML keeps only real exceptions, like the nightly tag and the owner in meta. Let's run dbt build on the whole project.

dbt_project.yml
name: shop
version: '1.0'
profile: shop

models:
  shop:
    staging:
      +materialized: view
    finance:
      +materialized: table
      +schema: finance
      +tags: ['finance']
    marketing:
      +materialized: table
      +schema: marketing
      +tags: ['marketing']
models/finance/_finance.yml
models:
  - name: fct_daily_revenue
    description: '{{ doc("fct_daily_revenue") }}'
    config:
      tags: ['nightly']
    columns:
      - name: payment_date
        description: Day the payment was captured (UTC).
      - name: revenue
        description: Sum of amount, before refunds.
models/marketing/_marketing.yml
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      meta:
        owner: marketing-analytics
Terminal
$ dbt build
 Succeeded seed  dbt_ali.payment_methods (table)
 Succeeded model dbt_ali.stg_payments (view)
    Passed test  not_null_stg_payments_method_name
    Passed test  not_null_stg_payments_payment_id
    Passed test  unique_stg_payments_payment_id
 Succeeded model dbt_ali_marketing.fct_card_share (table)
 Succeeded model dbt_ali_finance.fct_daily_revenue (table)
 Succeeded model dbt_ali_finance.fct_large_payments (table)

Finished 'build' successfully for target 'dev'
Processed: 4 models | 3 tests | 1 seed
Summary: 8 total | 8 success

Every model lands where we expect: fct_card_share in dbt_ali_marketing, both finance models in dbt_ali_finance as tables, and the staging tests pass. 8 total | 8 success, so nothing changed by accident.

Then enforce it:

  • dbt parse in CI: strict YAML on v2
  • The dbt_project_evaluator package: naming, tests and docs coverage
  • Slim CI: small, fast pull requests
Real project tip

Go one folder per pull request, most used models first. Small changes get reviewed. A thousand-file rewrite doesn't.

Key points to remember

  • Seeds: small static reference data; raw data is a source
  • The most specific config wins; tags add up
  • Jobs use tags and named selectors; check with dbt ls
  • Each target has its own schema; custom schemas get a prefix
  • dbt v2 fails on unknown YAML keys; docs live next to the code

The full script

Here is a Python script that writes the whole project and runs every dbt command above in order; run pip install dbt first.

Show the full script that builds this project and runs every step
Full script - pip install dbt, then run it with Python in an empty folder
# ========================================================================
# dbt Interview Q&A #7 - Sources, seeds and configuration
# TechBrothersIT. Sample data is invented for teaching.
# Every step below was run with dbt v2 (dbt OSS 2.0.8) on DuckDB; the outputs in the video and the
# blog post are the real outputs. To run it yourself, in an empty folder:
#   pip install dbt     (dbt v2; adapters are bundled, and DuckDB needs no account or server)
#   python dbt_Interview_QA_07_Code.py
# ========================================================================

import os, subprocess

def write(path, text):
    os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        f.write(text.strip("\n") + "\n")

def remove(path):
    if os.path.exists(path):
        os.remove(path)

def dbt(cmd, expect_error=False):
    print("\n$ " + cmd, flush=True)
    rc = subprocess.run(cmd, shell=True).returncode
    if rc != 0 and not expect_error:
        raise SystemExit("stopped: this command was expected to succeed")

# Step 0: the project and its sample data
write('dbt_project.yml', """\
name: shop
version: '1.0'
profile: shop

models:
  shop:
    +materialized: view
    finance:
      +schema: finance
      +tags: ['finance']
""")
write('profiles.yml', """\
shop:
  target: dev
  outputs:
    dev:
      type: duckdb
      path: warehouse.duckdb
      schema: dbt_ali
      threads: 4
    prod:
      type: duckdb
      path: warehouse.duckdb
      schema: analytics
      threads: 4
""")
write('macros/load_raw.sql', """\
{% macro load_raw() %}
  {# stands in for the loading tool: lands raw payments in the raw schema #}
  {% do run_query("create schema if not exists raw") %}
  {% do run_query("create or replace table raw.payments as select * from (values
      (1, 101, 'CC', 120.00, timestamp '2026-10-24 09:10:00'),
      (2, 102, 'PP', 45.50, timestamp '2026-10-24 11:40:00'),
      (3, 103, 'BT', 300.00, timestamp '2026-10-24 15:05:00'),
      (4, 104, 'DC', 80.25, timestamp '2026-10-25 08:30:00'),
      (5, 105, 'CC', 60.00, timestamp '2026-10-25 10:15:00'))
    as t(payment_id, order_id, method_code, amount, paid_at)") %}
{% endmacro %}
""")
write('seeds/payment_methods.csv', """\
code,method_name,is_card
CC,Credit card,true
DC,Debit card,true
PP,PayPal,false
BT,Bank transfer,false
""")
write('seeds/_seeds.yml', """\
seeds:
  - name: payment_methods
    description: Payment method codes. Changes a few times a year.
    config:
      column_types:
        code: varchar(2)
        method_name: varchar
        is_card: boolean
""")
write('models/staging/_sources.yml', """\
sources:
  - name: raw
    description: Landed by the loading tool, not by dbt.
    schema: raw
    tables:
      - name: payments
""")
write('models/staging/stg_payments.sql', """\
select
    p.payment_id,
    p.order_id,
    m.method_name,
    m.is_card,
    p.amount,
    p.paid_at
from {{ source('raw', 'payments') }} as p
join {{ ref('payment_methods') }} as m
    on m.code = p.method_code
""")
write('models/staging/_staging.yml', """\
models:
  - name: stg_payments
    description: One row per payment, with the method name.
    columns:
      - name: payment_id
        data_tests: [unique, not_null]
      - name: method_name
        data_tests: [not_null]
""")
write('models/finance/fct_daily_revenue.sql', """\
{{ config(materialized='view') }}  -- from debugging

select
    cast(paid_at as date) as payment_date,
    method_name,
    count(*) as payments,
    sum(amount) as revenue
from {{ ref('stg_payments') }}
group by 1, 2
""")
write('models/finance/_finance.yml', """\
models:
  - name: fct_daily_revenue
    config:
      materialized: table
      tags: ['nightly']
""")

# Step 1: The loading tool lands raw payments (a macro stands in for it)
dbt('dbt run-operation load_raw')

# Step 2: Load the reference seed
dbt('dbt seed')

# Step 3: Three levels set the materialization: which one wins?
dbt('dbt ls -s fct_daily_revenue --output json --output-keys name materialized tags')

# Step 4: Remove the config block: the YAML wins
write('models/finance/fct_daily_revenue.sql', """\
select
    cast(paid_at as date) as payment_date,
    method_name,
    count(*) as payments,
    sum(amount) as revenue
from {{ ref('stg_payments') }}
group by 1, 2
""")
dbt('dbt ls -s fct_daily_revenue --output json --output-keys name materialized tags')

# Step 5: Tags and a named selector
write('models/finance/fct_large_payments.sql', """\
select payment_id, method_name, amount
from {{ ref('stg_payments') }}
where amount >= 100
""")
write('models/marketing/fct_card_share.sql', """\
select
    count(*) filter (where is_card) / count(*) as card_share
from {{ ref('stg_payments') }}
""")
write('selectors.yml', """\
selectors:
  - name: nightly_finance
    description: The nightly finance job
    definition:
      method: tag
      value: finance
      parents: true
""")
dbt('dbt ls -s tag:finance')
dbt('dbt ls -s "tag:finance,tag:nightly"')
dbt('dbt ls --selector nightly_finance')

# Step 6: The same command in dev and in prod
dbt('dbt build -s +fct_daily_revenue')
dbt('dbt build -s +fct_daily_revenue --target prod')

# Step 7: A typo in a config key: dbt v2 fails the parse
write('models/marketing/_marketing.yml', """\
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      materalized: table
""")
dbt('dbt parse', expect_error=True)   # EXPECTED TO FAIL: the video shows this error

# Step 8: Fix the key; custom attributes go under meta
write('models/marketing/_marketing.yml', """\
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      materialized: table
      meta:
        owner: marketing-analytics
""")
dbt('dbt run -s fct_card_share')

# Step 9: Descriptions, a docs block and dbt docs generate
write('models/docs.md', """\
{% docs fct_daily_revenue %}
Revenue per day and payment method, in US dollars.
Refunds are not subtracted.
{% enddocs %}
""")
write('models/finance/_finance.yml', """\
models:
  - name: fct_daily_revenue
    description: '{{ doc("fct_daily_revenue") }}'
    config:
      materialized: table
      tags: ['nightly']
    columns:
      - name: payment_date
        description: Day the payment was captured (UTC).
      - name: revenue
        description: Sum of amount, before refunds.
""")
dbt('dbt ls -s fct_daily_revenue --output json --output-keys name description')
dbt('dbt docs generate')

# Step 10: Clean-up: folder-level configs, then build everything
write('dbt_project.yml', """\
name: shop
version: '1.0'
profile: shop

models:
  shop:
    staging:
      +materialized: view
    finance:
      +materialized: table
      +schema: finance
      +tags: ['finance']
    marketing:
      +materialized: table
      +schema: marketing
      +tags: ['marketing']
""")
write('models/finance/_finance.yml', """\
models:
  - name: fct_daily_revenue
    description: '{{ doc("fct_daily_revenue") }}'
    config:
      tags: ['nightly']
    columns:
      - name: payment_date
        description: Day the payment was captured (UTC).
      - name: revenue
        description: Sum of amount, before refunds.
""")
write('models/marketing/_marketing.yml', """\
models:
  - name: fct_card_share
    description: Share of payments made by card.
    config:
      meta:
        owner: marketing-analytics
""")
dbt('dbt build')
Next in this series

Part 8: Deployment, CI and Slim CI

The sample data on this page is invented for teaching. Every terminal output is the real output of the dbt command above it, run with dbt v2 (dbt OSS 2.0.8) on DuckDB; run times and node counters are left out, and node lines are listed in DAG order. Part of the Real Time dbt Interview Questions and Answers series on TechBrothersIT. Features, limits and their status (GA or preview) are current as of October 2026; check the official dbt documentation (docs.getdbt.com) for anything you quote in an interview.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.