Avas LogoAvas

Why Intermediate Models Are the Most Underrated Layer in dbt Projects

From Raw Data to Business Data Products (Part 5)

After building staging models, I felt like the hard part was done.

The data was clean.

The schema was consistent.

The transformations were standardized.

At that point, I thought I could directly start building final business models like:

  • Host360
  • Listing360
  • Executive dashboards

But as I started writing more SQL, a problem quickly appeared.

My queries were getting repetitive.

And worse:

The same business logic was being rewritten in multiple places.

That's when I realized something important:

Staging models clean data — but they don't organize business logic.

That responsibility belongs to a less obvious but extremely important layer:

Intermediate models.


The Problem Intermediate Models Solve

Without intermediate models, every final model (like Host360) starts to accumulate logic like:

  • Counting listings per host
  • Calculating occupancy rates
  • Aggregating calendar availability
  • Computing average review scores

At first, this seems fine.

But very quickly, problems appear:

  • Logic gets duplicated across models
  • Small metric differences appear between dashboards
  • Debugging becomes painful
  • SQL becomes harder to read and maintain

I ran into this almost immediately when building host-level metrics.


The Key Realization

Instead of asking:

"How do I build Host360?"

I had to step back and ask:

"What logic will Host360 depend on repeatedly?"

That question led me to intermediate models.

The idea is simple:

Intermediate models are where reusable business logic lives.

Not raw data.

Not final outputs.

But everything in between.


Intermediate Model 1: Host Listing Metrics

The first intermediate model I built was focused on understanding hosts.

Specifically:

  • How many listings does each host have?
  • What types of listings do they own?
  • What is their average property structure?

Here is the model:

WITH listings AS (
    SELECT *
    FROM {{ ref('stg_listings') }}
),

transformed AS (
    SELECT  
        host_id,

        COUNT(*) AS listing_count,

        MAX(homes_count) AS entire_home_count,
        MAX(private_rooms_count) AS private_room_count,
        MAX(shared_rooms_count) AS shared_room_count,

        AVG(accommodates) AS avg_accommodates,
        AVG(bedrooms) AS avg_bedrooms,
        AVG(beds) AS avg_beds,
        AVG(bathrooms) AS avg_bathrooms,

        AVG(minimum_nights) AS minimum_nights_avg

    FROM listings
    GROUP BY host_id
)

SELECT * FROM transformed;

Why this model matters

At first glance, this looks like a simple aggregation.

But conceptually, it does something important:

It transforms "listings data" into "host behavior data."

This is the first step where the grain changes meaningfully:

  • Before → listing-level data
  • Now → host-level behavior

And this transformation becomes reusable.

Instead of recalculating host metrics in every downstream model, this logic is computed once.


Intermediate Model 2: Host Calendar Metrics

The second intermediate model focuses on a different dimension:

how hosts perform over time.

This is where availability, occupancy, and booking patterns come in.

Instead of working at the listing level, I aggregated everything to the host level.

WITH listing_calendar_metrics AS (
    SELECT *
    FROM {{ ref('int_listing_calendar_metrics') }}
),

transformed AS (
    SELECT
        host_id,

        SUM(available_days) AS available_days,
        SUM(booked_days) AS booked_days,
        SUM(total_days) AS total_days,

        -- Host-level occupancy rate
        ROUND(
            SUM(booked_days) * 100.0 / NULLIF(SUM(total_days), 0),
            2
        ) AS occupancy_rate,

        -- Weighted average of minimum nights
        ROUND(
            SUM(sum_minimum_nights_available) * 1.0 /
            NULLIF(SUM(available_days), 0),
            2
        ) AS avg_minimum_nights_available

    FROM listing_calendar_metrics
    GROUP BY host_id
)

SELECT * FROM transformed;

Why this model is more interesting

This model is where the logic becomes mathematically and conceptually important.

1. Occupancy rate is not a simple average

SUM(booked_days) / SUM(total_days)

This ensures correctness at scale.

If I had computed occupancy at the listing level and then averaged it, I would get a biased result.

This is a classic analytics engineering mistake.

2. Weighted aggregation matters

SUM(sum_minimum_nights_available) / SUM(available_days)

This avoids treating all listings equally when they have different availability distributions.

This is where intermediate models become powerful:

They encode correct business logic once, instead of repeating it everywhere incorrectly.


The Real Purpose of Intermediate Models

After building these two models, the pattern became clear.

Intermediate models are not about performance.

They are about shared understanding of business logic.

They solve three key problems:

1. Reusability

Without intermediate models:

  • Every dashboard recalculates host metrics
  • Every model duplicates logic

With intermediate models:

  • Logic is computed once and reused everywhere

2. Correctness

Some metrics are mathematically sensitive:

  • Ratios
  • Weighted averages
  • Aggregations across multiple grains

Intermediate models ensure these are calculated correctly once, not inconsistently across models.

3. Readability

Final models like Host360 become much simpler:

Instead of:

200 lines of aggregation logic

We get:

SELECT *
FROM int_host_metrics
JOIN int_host_calendar_metrics

How This Fits Into the Airbnb Project

At this point, the architecture looks like this:

RAW
  ↓
STAGING
  ↓
INTERMEDIATE MODELS
      ├── host metrics
      ├── listing metrics
      ├── calendar metrics
  ↓
MARTS (Host360, Listing360)

The important shift is:

Intermediate models are where business logic becomes stable.


What Changed in My Thinking

Before intermediate models:

"I'll compute everything inside Host360."

After intermediate models:

"Host360 should only assemble pre-computed, trusted logic."

That shift completely changed how I approached dbt.

I stopped thinking in terms of:

single big queries

and started thinking in terms of:

reusable building blocks


What We Have Built So Far

At this point in the project:

  • Raw data is ingested into Snowflake
  • Staging models standardize structure
  • Intermediate models encode business logic

Now we finally have something important:

A system where business logic is reusable, consistent, and testable.


Looking Ahead

In the next article, we'll move into the most conceptually important modeling layer:

Facts vs Dimensions in Airbnb Data

This is where we define:

  • What actually counts as a "fact" in this system
  • How listing, host, and calendar data should be structured
  • And why intermediate models are not enough on their own

And most importantly:

How these layers come together to form the foundation of Host360.