In a dbt source definition, what do database, schema and identifier control?
answer
- three parts make one relation
- two of them have surprising defaults
- the source name doubles as something
- name defaults to schema, table to identifier
basics
~20 sThey form the physical relation dbt compiles a source() call into. The source name defaults to the schema and the table name defaults to the identifier, so you set schema or identifier only when the warehouse names differ from the names you want in code.
solid answer
~40 sA `source()` call compiles to `database.schema.identifier`. Each part has a default: `database` falls back to the target database from your profile, `schema` falls back to the **source name**, and `identifier` falls back to the **table name**. So a source named `jaffle_shop` with a table named `orders` resolves to `<target_database>.jaffle_shop.orders` with no extra keys at all. You override them when the physical names are ugly or unstable: `database: raw` when landing data lives in a separate database, `schema: fivetran_jaffle` when the loader chose the schema, and `identifier: orders_v2` when the real table name is not what you want to type in every model. The names in `source('jaffle_shop', 'orders')` stay stable while the overrides absorb warehouse reality — a rename upstream is one YAML line, not a find-and-replace across models.
code
yaml · 11 linesversion: 2
sources:
- name: jaffle_shop # logical name used in source()
database: raw # override: landing database
schema: fivetran_jaffle # override: loader chose this schema
loader: fivetran
tables:
- name: orders # logical table name
identifier: orders_v2 # override: real table name
- name: customers # no override -> raw.fivetran_jaffle.customersgo deeper
Know that a source() call turns into a real table name and that the YAML block is where that mapping lives. Be able to read a source block and say which table it points at.
State the three keys and each default precisely, especially that schema falls back to the source name. Explain why you would override identifier and what an upstream rename then costs you.
Show how you keep environments apart with templated schema or database values, and how you diagnose a bad resolution from compiled SQL rather than guessing at casing.
Own the convention across projects: which sources exist, how landing databases map to logical names, and how a loader migration is absorbed in YAML without touching model code or breaking lineage.
## The compiled relation Every `source()` call in dbt resolves to a three-part relation on most warehouses: ``` database . schema . identifier ``` The two arguments you pass — `source('jaffle_shop', 'orders')` — are *logical* names. The three YAML keys are what maps those logical names onto the physical object. Understanding the defaults is the whole question. ## The defaults - **`database`** defaults to the database configured for the current target in your connection profile. Set it explicitly when raw data lands in a separate database from the one dbt writes to, which is common on Snowflake (`RAW` vs `ANALYTICS`) and Redshift-style setups. On warehouses with a two-part namespace the key is generally not used. - **`schema`** defaults to the **source's `name`**. This is the one people misremember; they assume it falls back to the target schema, which would be nonsense — the target schema is where dbt *writes*, not where raw data sits. - **`identifier`** defaults to the **table's `name`**. So the minimal declaration below resolves `source('jaffle_shop', 'orders')` to `<target_database>.jaffle_shop.orders`: ```yaml sources: - name: jaffle_shop tables: - name: orders ``` ## When you override each key **database** — the landing zone is physically separate. `database: raw` is the archetype. It also lets a single project read from several databases, one per source system. **schema** — the loader picked the schema and you do not like it, or it differs from the friendly name. An ingestion tool may write to `fivetran_jaffle_shop_public`; you declare `name: jaffle_shop` with `schema: fivetran_jaffle_shop_public` and every model still says `source('jaffle_shop', ...)`. If the schema differs per environment, the value can be templated from a variable or an environment variable so dev points at a sample schema and prod at the real one. **identifier** — the physical table name is versioned, prefixed, reserved, or awkward: `orders_v2`, `tbl_orders`, `ORDERS$RAW`. Declare `name: orders` with `identifier: orders_v2`. When the upstream team ships `orders_v3`, you change one line and every downstream model follows. ## Why this indirection is the point The logical names are the contract your SQL is written against; the physical names are the warehouse's business. Keeping them separate means an upstream rename, a loader migration, or a database reorganisation is a YAML edit rather than a repo-wide search. It also keeps model code readable — `source('salesforce', 'account')` says more than `SFDC_PROD_REPL.RAW_SFDC_V2.ACCOUNT__C`. ## Other keys that live alongside them A source block carries more than location. `description` documents the system; `loader` records what wrote the data (a free-text label that shows in docs); `tables` can each carry `description`, `columns` with tests, `loaded_at_field` and a `freshness` block. Source-level defaults cascade to tables, so setting `database` and `schema` once on the source covers every table beneath it unless a table overrides them. ## Casing and quoting Warehouses disagree about identifier case. Snowflake upper-cases unquoted identifiers, Postgres lower-cases them, BigQuery is case-sensitive for table names. If a raw table was created with quotes and mixed case, the plain value in YAML may not match. The `quoting` config (settable at the source or table level for `database`, `schema` and `identifier`) controls whether dbt wraps each part in quotes when it compiles. A source that resolves to a relation the warehouse insists does not exist is nearly always a casing or quoting problem, and the fastest diagnosis is to compile the model and read the exact SQL dbt generated. ## Diagnosing a bad resolution When a source-based model fails with "relation does not exist", do not guess. Run a compile and open the compiled SQL, or query the generated manifest — the compiled `FROM` clause shows precisely which three parts dbt assembled. Nine times in ten the answer is that `schema` was omitted and silently defaulted to the source name, or that the physical table has a suffix the `identifier` key was never told about. ## The rule to carry away Source name and table name are what your code says. Database, schema and identifier are where the data actually is. If the two ever have to be the same string, you have simply accepted the defaults.
- A source model fails with "relation does not exist" — how do you find out what dbt actually looked for?Compile the model and read the generated SQL; the `FROM` clause shows the exact three-part relation dbt assembled. Compare it against the warehouse's information schema. The usual causes are an omitted `schema` key silently defaulting to the source name, a missing `identifier` when the physical table has a version suffix, or an identifier-casing mismatch on a case-sensitive warehouse.
- How do you point the same source at a sample schema in dev and the real one in production?Template the `schema` (or `database`) value from a variable or environment variable resolved per target, so the key renders differently in each environment while the source name in code stays fixed. Keep the switch in one place; do not fork the model SQL, since that reintroduces the hardcoding the source block exists to remove.
- If several tables in a source live in the same schema, do you repeat the keys on each one?No. `database` and `schema` set at the source level cascade to every table beneath it, and a table only declares its own value when it genuinely differs. `identifier` is inherently per-table, since it names one object.
saying these in an interview costs you the question
- Thinks schema defaults to the target schema from the profile
- Believes identifier renames the table in the warehouse
- Cannot explain what a source() call compiles to
- Says the source name must equal the physical schema name
- Treats database, schema and identifier as required on every source