dbt Ephemeral Models

An ephemeral model does not create any database object in your warehouse. Instead, dbt inlines the model's SQL as a Common Table Expression (CTE) inside every model that references it. Ephemeral models let you organize your SQL logic into small, readable pieces without cluttering your warehouse with intermediate tables or views.

A Grocery Store Analogy

View / Table:    "Make guacamole, put it in a bowl, store in fridge, serve from bowl"
Ephemeral:       "Mash the avocados directly into the dish as you assemble it"

The ephemeral model represents a step in your cooking process that happens inline. It never becomes its own stored item. Downstream models consume the logic, not a stored result.

Creating an Ephemeral Model

-- models/intermediate/int_orders_enriched.sql

{{ config(materialized='ephemeral') }}

select
    o.order_id,
    o.customer_id,
    o.order_date,
    o.amount_dollars,
    case
        when o.amount_dollars >= 500 then 'high_value'
        when o.amount_dollars >= 100 then 'medium_value'
        else 'low_value'
    end as order_tier,
    datediff(day, o.order_date, current_date) as days_since_order
from {{ ref('stg_orders') }} o

Referencing the Ephemeral Model

-- models/marts/fct_orders.sql

{{ config(materialized='table') }}

select
    e.order_id,
    e.order_tier,
    e.days_since_order,
    c.full_name    as customer_name,
    c.email
from {{ ref('int_orders_enriched') }} e    ← references the ephemeral model
join {{ ref('stg_customers') }}       c
  on e.customer_id = c.customer_id

What the Compiled SQL Looks Like

When dbt compiles fct_orders.sql, it inlines int_orders_enriched as a CTE:

-- target/compiled/fct_orders.sql (simplified)

with int_orders_enriched as (
    -- contents of int_orders_enriched.sql inlined here
    select
        o.order_id,
        o.customer_id,
        o.order_date,
        o.amount_dollars,
        case
            when o.amount_dollars >= 500 then 'high_value'
            when o.amount_dollars >= 100 then 'medium_value'
            else 'low_value'
        end as order_tier,
        datediff(day, o.order_date, current_date) as days_since_order
    from dbt_dev.stg_orders o
)

select
    e.order_id,
    e.order_tier,
    e.days_since_order,
    c.full_name as customer_name,
    c.email
from int_orders_enriched e
join dbt_dev.stg_customers c
    on e.customer_id = c.customer_id

The warehouse sees one combined query. It never creates a separate table or view for int_orders_enriched.

Ephemeral Models in the DAG

Ephemeral models still appear in dbt's DAG and in the documentation site. They show as a distinct node type (dotted outline in dbt Cloud). You can still see their dependencies and which models use them.

[stg_orders] ──────────────────────────────────────────────────────────>
                                                                         [fct_orders]
[int_orders_enriched] (ephemeral, inlined into fct_orders) ────────────>
[stg_customers] ────────────────────────────────────────────────────────>

Chaining Ephemeral Models

One ephemeral model can reference another ephemeral model. dbt chains the CTEs together in the final compiled SQL:

-- int_step1 (ephemeral): filters raw orders
-- int_step2 (ephemeral): enriches filtered orders (references int_step1)
-- fct_final (table): references int_step2

Compiled SQL for fct_final:
  with int_step1 as (...),
       int_step2 as (...references int_step1 as CTE...),
  select * from int_step2 ...

Limitations of Ephemeral Models

Cannot be queried directly

Because no warehouse object is created, you cannot run SELECT * FROM int_orders_enriched in your warehouse to inspect the data. To debug an ephemeral model, temporarily switch it to a view, inspect the output, then switch back.

Cannot be used by multiple unrelated downstream models efficiently

If three independent downstream models all reference the same ephemeral model, the ephemeral SQL is inlined (and re-run) in each of the three queries separately. A view or table would be computed once and reused. For shared logic used by many models, a view is more efficient than ephemeral.

Cannot run tests on intermediate data

Because no table exists, you cannot run a not_null test on an ephemeral model's columns. Tests on ephemeral models run against the inlined CTE, which some configurations do not support.

When to Use Ephemeral

Use ephemeral when:                    Use view instead when:
----------------------------           -----------------------
Logic is only used by one model        Logic is used by multiple models
No need to debug intermediate data     You want to query intermediate data
Cleaning up warehouse object count     Tests are needed on intermediate data
Short, simple transformations          Complex transformations worth inspecting

Leave a Comment

Your email address will not be published. Required fields are marked *