dbt

dbt Builtins: Correct Usage of this and Practical Dynamic Reference Patterns

2026-04-19
NicheeLab Editorial Team

this is a dbt built-in variable that returns a Relation representing the node currently being compiled and executed (model, snapshot, test, hook, and so on).

This article walks through how this works, how it differs from ref/source, how to use it in incremental models, and the caveats in tests and run-operation — organized around the points most likely to appear on the exam.

Fundamentals: this is the Relation of the current node

this is a built-in variable available in the Jinja context that returns an adapter-specific Relation object pointing to the current node. At render time it expands to database.schema.identifier (with the adapter applying the appropriate quoting).

You can access this.database, this.schema, and this.identifier individually. It is available in models, snapshots, tests, and hooks, but using it inside an ephemeral model is discouraged because such models have no physical object and the meaning is ambiguous.

  • Availability: models, snapshots, tests, and pre/post-hooks on seeds and models
  • Return type: adapter-specific Relation (project.dataset.table on BigQuery, database.schema.identifier on Snowflake)
  • Attributes: database / schema / identifier (database may be omitted in some environments)
  • Main uses: target of MERGE/DELETE/GRANT, and self-references inside hooks and tests
SyntaxPurposeExample rendered output
thisPoints to the Relation of the current nodeanalytics.core.orders
ref('model_name')Resolves dependent models and forms the DAGanalytics.staging.stg_customers
source('src', 'table')References an external source tableraw.sales.customers
targetMetadata about the execution target (profile, schema, etc.)dev (not a Relation)

How Jinja renders this

model.sql (Jinja)select * from {{ this }}dbt compilerDependency resolution (ref etc.) / Relation expansionadapter execution layer"db.schema.table"model.sql (Jinja) → dbt compiler → adapter execution layer

Minimal example: referencing this attributes

-- models/demo_this.sql
-- At run time, inspect the components of the current model
select
  '{{ this.database }}' as db,
  '{{ this.schema }}'   as sch,
  '{{ this.identifier }}' as id

Production: using this for MERGE/DELETE in incremental models

Incremental models need to reconcile the existing target table with newly staged rows. Because this always points to the model's final Relation, it is safe to use as the target of MERGE/DELETE/INSERT.

Major adapters such as BigQuery, Snowflake, and Databricks all handle quoting and full qualification of {{ this }} correctly. Dialect differences show up in WHEN clauses and IDENTIFIER quoting, but the role of this itself is uniform.

  • Putting {{ this }} as the MERGE target makes it easy to absorb environment differences (database/schema names)
  • Combine with is_incremental() to branch between SELECT (full reload) and MERGE (incremental)
  • Consider syncing added columns via on_schema_change (depends on adapter support)

Standard MERGE pattern for incremental models

-- models/fct_orders.sql
{{ config(materialized='incremental', unique_key='order_id', on_schema_change='sync_all_columns') }}

with staged as (
  select * from {{ ref('stg_orders') }}
)

{% if is_incremental() %}
merge into {{ this }} as t
using staged as s
on t.order_id = s.order_id
when matched then update set
  updated_at = s.updated_at,
  total_amount = s.total_amount
when not matched then insert (order_id, updated_at, total_amount)
values (s.order_id, s.updated_at, s.total_amount)
{% else %}
select * from staged
{% endif %}

Tests: this is the test node, model is the tested model

Inside a generic test, these two are often confused. this refers to the test itself (the temporary test relation dbt generates), while model refers to the tested model (the Relation resolved via ref).

When you write a custom column test, keeping the split clear — read from model, write the aggregated output to this — avoids most of the confusion.

  • Reading the tested data: use {{ model }}
  • Destination for test results: this (typically managed by dbt)
  • Mistakenly using this as the data input can result in unexpected empty sets or self-references

Safe pattern for a generic test (not-null example)

-- tests/generic/not_null.sql (example)
-- args: model, column_name
with src as (
  select * from {{ model }}
)
select
  count_if({{ column_name }} is null) as null_cnt
from src
-- this is the test node (output destination). Aggregate results are stored here.

Hooks and ephemeral: where this is available

pre-hooks and post-hooks (on models, snapshots, and seeds) can use this. A common pattern is to run GRANT statements or statistics updates against this in a post-hook.

Avoid using this inside ephemeral models since they do not create a physical object. Ephemerals are inlined into downstream models and are not tied to an explicit Relation.

  • post-hook example: grant select on {{ this }} to role ...
  • Seeds also have hooks, and this points to the table created by the seed
  • this has no meaningful value inside an ephemeral, so reconsider the design (use materialized='view'/'table' if needed)

GRANT in a post-hook (Snowflake example)

-- models/dim_customers.sql
{{ config(
  materialized='table',
  post_hook=[
    "grant select on {{ this }} to role ANALYST"
  ]
) }}

select * from {{ ref('stg_customers') }}

Macros and run-operation: handling cases where this is undefined

When you invoke a macro standalone via dbt run-operation, there is no current node context and this is undefined. To work with a Relation, accept database/schema/identifier as arguments and resolve via adapter.get_relation.

this is available when a macro is called from a model, but to make a macro reusable and general-purpose, it is safer to accept the Relation as an explicit argument.

  • Do not assume this exists in run-operation
  • Fetch an existing Relation with adapter.get_relation, and branch into create logic when it does not exist
  • Designs that accept a Relation string or its components (db/schema/id) as arguments are most robust

For run-operation: handle the Relation via explicit arguments

-- macros/admin.sql
{% macro drop_if_exists(database, schema, identifier) %}
  {% set rel = adapter.get_relation(database=database, schema=schema, identifier=identifier) %}
  {% if rel is not none %}
    {{ adapter.drop_relation(rel) }}
  {% else %}
    {{ log('relation not found: ' ~ database ~ '.' ~ schema ~ '.' ~ identifier, info=True) }}
  {% endif %}
{% endmacro %}

-- Example: dbt run-operation drop_if_exists --args '{"database": "ANALYTICS", "schema": "CORE", "identifier": "OLD_TABLE"}'

Exam tips: common points of confusion and checks

On the Analytics Engineer exam, the roles of ref/source/this, the right place to use this in incremental models, and the difference between this and model in generic tests are all frequent topics.

Lock in the basics — "this is always the current node," "ref resolves dependencies and forms the DAG," "source explicitly marks external systems" — and be ready to explain how this can be undefined in hook and macro contexts.

  • this is a Relation; target is metadata (not a Relation)
  • Generic tests: read data via model, write results to this
  • Incremental: use {{ this }} as the MERGE/DELETE target (the adapter handles dialect differences)
  • Do not use this in ephemerals
  • Do not expect this in run-operation (arguments are required)

Attribute-inspection snippet (for study)

-- models/_debug_this.sql (handy for learning)
select
  '{{ this }}' as relation,
  '{{ this.database if this is not none else "" }}' as db,
  '{{ this.schema if this is not none else "" }}'   as sch,
  '{{ this.identifier if this is not none else "" }}' as id

Check your understanding

Analytics Engineer

問題 1

When running an incremental model and you want to safely MERGE against the existing table, which use of a dbt built-in variable is most appropriate?

  1. Use {{ this }} as the MERGE target and reference the staging side via a CTE/subquery
  2. Use {{ ref('stg_table') }} as the MERGE target
  3. Hand-build the target as a string: target.database || '.' || target.schema || '.' || 'table'
  4. Use {{ source('raw','table') }} as the MERGE target

正解: A

For incremental models, the correct answer is putting {{ this }} — which points to your own existing Relation — as the MERGE target. ref and source point to different resources, and target is not a Relation, so neither fits. Hand-built string concatenation bypasses the adapter's quoting and full-qualification logic and is unsafe.

Frequently Asked Questions

Which file types support this?

It is available in models, snapshots, test SQL, and hooks (pre/post). It is not available in seed CSV files themselves, but it does work inside hooks attached to seeds. Macros invoked standalone via run-operation do not have this defined.

Does this behave differently across adapters like BigQuery or Snowflake?

The meaning of this (the Relation of the current node) is consistent across adapters, and each adapter handles fully qualified naming and quoting appropriately at render time. BigQuery uses project.dataset.table and Snowflake uses database.schema.identifier, but you can use {{ this }} as-is in either case.

Can I reference the tested data by using this in a generic test?

No. In a generic test, this refers to the test node (where results are written), while the tested model is referenced via model. Use {{ model }} to read the data being tested.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
dbt

dbt Models: SQL-Defined Transformation Units (2026)

Model fundamentals — SELECT-based definitions, naming, refs,...

dbt

dbt Analytics Engineering Exam: Complete Guide (2026)

Pass the AE Certification — scope, weighting, sample questio...

dbt

dbt Cloud vs dbt Core: Feature & Cost Comparison (2026)

Honest comparison of dbt Cloud vs. dbt Core — IDE, scheduler...

dbt

dbt Project Structure: models/seeds/macros Layout (2026)

Recommended dbt project layout — models, seeds, macros, snap...

dbt

dbt_project.yml Explained: Every Config (2026)

Every dbt_project.yml setting that matters — paths, vars, ma...

Browse all dbt articles (101)
© 2026 NicheeLab All rights reserved.