Loading...
Loading...
Inside Airbnb data warehouse built with Snowflake and dbt, demonstrating modern analytics engineering patterns with staging, intermediate, and mart layers.
npx skill4agent add aradotso/data-skills snowflake-dbt-airbnb-analyticsSkill by ara.so — Data Skills collection.
listings.csv.gzcalendar.csv.gzreviews.csv.gzneighbourhoods.csv# Clone and set up environment
git clone https://github.com/analyticsdurgesh/Snowflake_DBT_Project.git
cd Snowflake_DBT_Project
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp profiles.yml.example profiles.yml
cp config/local_credentials.example.json config/local_credentials.jsonprofiles.ymlairbnb_snowflake:
target: dev
outputs:
dev:
type: snowflake
account: YOUR_ACCOUNT
user: YOUR_USER
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
role: YOUR_ROLE
database: AIRBNB_DB
warehouse: COMPUTE_WH
schema: ANALYTICS
threads: 4
client_session_keep_alive: Falseconfig/local_credentials.json{
"account": "YOUR_ACCOUNT",
"user": "YOUR_USER",
"password": "YOUR_PASSWORD",
"role": "YOUR_ROLE",
"warehouse": "COMPUTE_WH",
"database": "AIRBNB_DB",
"schema": "ANALYTICS"
}export SNOWFLAKE_PASSWORD="your_password"data/raw/data/raw/listings.csv.gz
data/raw/calendar.csv.gz
data/raw/reviews.csv.gz
data/raw/neighbourhoods.csvpython scripts/load_inside_airbnb_to_snowflake.pysetup/snowflake_setup.sqlINSIDE_AIRBNB_STAGERAWimport snowflake.connector
import json
# Load credentials
with open('config/local_credentials.json') as f:
creds = json.load(f)
# Connect to Snowflake
conn = snowflake.connector.connect(
account=creds['account'],
user=creds['user'],
password=creds['password'],
role=creds['role'],
warehouse=creds['warehouse']
)
# Upload to stage
conn.cursor().execute(f"PUT file://data/raw/listings.csv.gz @INSIDE_AIRBNB_STAGE")
# Copy into raw table
conn.cursor().execute("""
COPY INTO RAW.LISTINGS
FROM @INSIDE_AIRBNB_STAGE/listings.csv.gz
FILE_FORMAT = (TYPE = 'CSV' SKIP_HEADER = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '"')
""")| Layer | Path | Purpose | Example |
|---|---|---|---|
| Sources | | Define raw tables | |
| Staging | | Clean, cast, standardize | |
| Intermediate | | Joins, enrichment, business logic | |
| Marts | | Dimensions, facts, aggregates | |
models/staging/stg_airbnb__listings.sqlwith source as (
select * from {{ source('airbnb_raw', 'listings') }}
),
cleaned as (
select
id::bigint as listing_id,
name::varchar as listing_name,
host_id::bigint as host_id,
host_name::varchar as host_name,
neighbourhood_cleansed::varchar as neighbourhood,
room_type::varchar as room_type,
price::varchar as price_raw,
minimum_nights::int as minimum_nights,
number_of_reviews::int as number_of_reviews,
last_review::date as last_review_date,
reviews_per_month::float as reviews_per_month,
availability_365::int as availability_365
from source
)
select * from cleaned{{ source() }}::models/intermediate/int_airbnb__calendar_enriched.sqlwith calendar as (
select * from {{ ref('stg_airbnb__calendar') }}
),
listings as (
select * from {{ ref('int_airbnb__listing_enriched') }}
),
enriched as (
select
c.listing_id,
c.calendar_date,
c.available,
c.price,
c.adjusted_price,
c.minimum_nights,
c.maximum_nights,
l.listing_name,
l.neighbourhood,
l.room_type,
l.host_id,
l.host_name,
-- Revenue proxy: price when unavailable
case
when c.available = false and c.price > 0
then c.price
else 0
end as estimated_revenue
from calendar c
left join listings l
on c.listing_id = l.listing_id
)
select * from enriched{{ ref() }}models/marts/fct_listing_calendar.sql{{
config(
materialized='incremental',
unique_key=['listing_id', 'calendar_date'],
merge_update_columns=['available', 'price', 'estimated_revenue']
)
}}
with calendar_enriched as (
select * from {{ ref('int_airbnb__calendar_enriched') }}
)
select
listing_id,
calendar_date,
available,
price,
adjusted_price,
minimum_nights,
maximum_nights,
neighbourhood,
room_type,
host_id,
estimated_revenue
from calendar_enriched
{% if is_incremental() %}
where calendar_date > (select max(calendar_date) from {{ this }})
{% endif %}materialized='incremental'unique_keymerge_update_columnsis_incremental()--full-refreshmodels/marts/agg_neighbourhood_monthly_performance.sqlwith listing_monthly as (
select * from {{ ref('agg_listing_monthly_performance') }}
)
select
neighbourhood,
year_month,
count(distinct listing_id) as total_listings,
sum(total_days) as total_days,
sum(available_days) as total_available_days,
sum(unavailable_days) as total_unavailable_days,
round(avg(availability_rate), 2) as avg_availability_rate,
round(sum(estimated_revenue), 2) as total_estimated_revenue,
round(avg(avg_price), 2) as avg_listing_price
from listing_monthly
group by neighbourhood, year_month
order by neighbourhood, year_monthround()# Test connection
dbt debug --profiles-dir .
# Run all models
dbt run --profiles-dir .
# Run specific model and downstream dependencies
dbt run --select dim_listings+ --profiles-dir .
# Run incremental models with full refresh
dbt run --full-refresh --select fct_listing_calendar+ --profiles-dir .
# Run tests
dbt test --profiles-dir .
# Test specific model
dbt test --select stg_airbnb__listings --profiles-dir .
# Generate and serve documentation
dbt docs generate --profiles-dir .
dbt docs serve --profiles-dir .# New data load workflow
python scripts/load_inside_airbnb_to_snowflake.py
dbt run --full-refresh --select fct_listing_calendar+ --profiles-dir .
dbt test --profiles-dir .
# Development workflow (iterative)
dbt run --select +fct_reviews --profiles-dir . # Run model and upstream deps
dbt test --select fct_reviews --profiles-dir .models/staging/schema.ymlversion: 2
models:
- name: stg_airbnb__listings
columns:
- name: listing_id
tests:
- unique
- not_null
- name: room_type
tests:
- accepted_values:
values: ['Entire home/apt', 'Private room', 'Shared room', 'Hotel room']
- name: price
tests:
- not_null
- dbt_utils.expression_is_true:
expression: ">= 0"
- name: stg_airbnb__calendar
columns:
- name: listing_id
tests:
- relationships:
to: ref('stg_airbnb__listings')
field: listing_idtests/no_duplicate_listing_dates.sql-- Test for duplicate listing-date combinations in fact table
select
listing_id,
calendar_date,
count(*) as record_count
from {{ ref('fct_listing_calendar') }}
group by listing_id, calendar_date
having count(*) > 1schema.ymltests/dbt_utilspackages.ymlpackages:
- package: dbt-labs/dbt_utils
version: 1.1.1dbt deps --profiles-dir .dashboard/streamlit_app.pyimport streamlit as st
import snowflake.connector
import pandas as pd
import json
# Load credentials
with open('config/local_credentials.json') as f:
creds = json.load(f)
@st.cache_resource
def get_connection():
return snowflake.connector.connect(
account=creds['account'],
user=creds['user'],
password=creds['password'],
role=creds['role'],
warehouse=creds['warehouse'],
database=creds['database'],
schema=creds['schema']
)
def run_query(query):
conn = get_connection()
return pd.read_sql(query, conn)
st.title("Inside Airbnb Analytics Dashboard")
# Neighbourhood performance
st.header("Top Neighbourhoods by Estimated Revenue")
query = """
SELECT
neighbourhood,
total_estimated_revenue,
avg_availability_rate,
total_listings
FROM agg_neighbourhood_monthly_performance
WHERE year_month = (SELECT MAX(year_month) FROM agg_neighbourhood_monthly_performance)
ORDER BY total_estimated_revenue DESC
LIMIT 10
"""
df = run_query(query)
st.dataframe(df)
st.bar_chart(df.set_index('NEIGHBOURHOOD')['TOTAL_ESTIMATED_REVENUE'])
# Room type pricing
st.header("Average Price by Room Type")
query = """
SELECT
room_type,
ROUND(AVG(price), 2) as avg_price
FROM dim_listings
WHERE price > 0
GROUP BY room_type
ORDER BY avg_price DESC
"""
df = run_query(query)
st.bar_chart(df.set_index('ROOM_TYPE')['AVG_PRICE'])streamlit run dashboard/streamlit_app.py@st.cache_resourceMAX(year_month)models/staging/sources.ymlsources:
- name: airbnb_raw
database: AIRBNB_DB
schema: RAW
tables:
- name: new_tablemodels/staging/stg_airbnb__new_table.sqlwith source as (
select * from {{ source('airbnb_raw', 'new_table') }}
),
cleaned as (
select
id::bigint as record_id,
field::varchar as clean_field
from source
)
select * from cleanedmodels/staging/schema.ymlmodels:
- name: stg_airbnb__new_table
columns:
- name: record_id
tests:
- unique
- not_nullmodels/marts/dim_hosts.sqlwith listings as (
select * from {{ ref('int_airbnb__listing_enriched') }}
),
host_agg as (
select
host_id,
max(host_name) as host_name,
count(*) as total_listings,
round(avg(price), 2) as avg_listing_price,
sum(number_of_reviews) as total_reviews
from listings
group by host_id
)
select * from host_aggmax()with daily_facts as (
select * from {{ ref('fct_listing_calendar') }}
)
select
listing_id,
to_char(calendar_date, 'YYYY-MM') as year_month,
count(*) as total_days,
sum(case when available then 1 else 0 end) as available_days,
sum(case when not available then 1 else 0 end) as unavailable_days,
round(avg(case when available then 1.0 else 0.0 end), 2) as availability_rate,
round(sum(estimated_revenue), 2) as estimated_revenue,
round(avg(price), 2) as avg_price
from daily_facts
group by listing_id, to_char(calendar_date, 'YYYY-MM')to_char(date, 'YYYY-MM')avg(case when condition then 1.0 else 0.0 end)Database Error in model [...] (...) 250001 (08001): Failed to connect to DBprofiles.ymldbt debug --profiles-dir .# Force full rebuild
dbt run --full-refresh --select fct_listing_calendar --profiles-dir .unique_key{{
config(
unique_key=['listing_id', 'calendar_date'] -- Must match table grain
)
}}dbt_utils.expression_is_true-- Remove $ and commas, cast to numeric
replace(replace(price, '$', ''), ',', '')::decimal(10,2) as priceOperationalError: 250001 (08001): Failed to connectconfig/local_credentials.jsonconn = snowflake.connector.connect(
...,
login_timeout=30,
network_timeout=30
)File not founddata/raw/.gzCompilation Error: Model 'X' depends on a node named 'Y' which was not found{{ ref('model_name') }}dbt deps --profiles-dir .models/profiles.ymlairbnb_snowflake:
target: prod
outputs:
prod:
type: snowflake
account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}"
user: "{{ env_var('SNOWFLAKE_USER') }}"
password: "{{ env_var('SNOWFLAKE_PASSWORD') }}"
role: "{{ env_var('SNOWFLAKE_ROLE') }}"
database: "{{ env_var('SNOWFLAKE_DATABASE') }}"
warehouse: "{{ env_var('SNOWFLAKE_WAREHOUSE') }}"
schema: ANALYTICS
threads: 4import os
conn = snowflake.connector.connect(
account=os.getenv('SNOWFLAKE_ACCOUNT'),
user=os.getenv('SNOWFLAKE_USER'),
password=os.getenv('SNOWFLAKE_PASSWORD'),
role=os.getenv('SNOWFLAKE_ROLE'),
warehouse=os.getenv('SNOWFLAKE_WAREHOUSE'),
database=os.getenv('SNOWFLAKE_DATABASE'),
schema='ANALYTICS'
)