snowflake-dbt-airbnb-analytics
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSnowflake dbt Airbnb Analytics
Snowflake dbt Airbnb Analytics
Skill by ara.so — Data Skills collection.
This project demonstrates a complete analytics engineering workflow using Snowflake, dbt, and Streamlit. It loads Inside Airbnb open data into Snowflake, transforms it through a layered dbt architecture (staging → intermediate → marts), validates data quality with tests, and serves insights via a Streamlit dashboard.
由ara.so提供的技能——数据技能合集。
本项目展示了一个使用Snowflake、dbt和Streamlit的完整分析工程工作流。它将Inside Airbnb开放数据加载到Snowflake中,通过分层dbt架构(staging → intermediate → marts)进行转换,用测试验证数据质量,并通过Streamlit仪表板呈现洞察结果。
What This Project Does
项目功能
- Raw data ingestion: Loads CSV/GZIP files from Inside Airbnb into Snowflake internal stages
- Layered transformations: Implements staging (clean/cast), intermediate (joins/enrichment), and mart (dimensions/facts) layers
- Incremental modeling: Uses Snowflake merge strategy for fact tables
- Data quality: Generic and singular dbt tests validate uniqueness, relationships, and business rules
- Analytics dashboard: Streamlit app queries marts for neighbourhood and listing performance
Data sources: , , , from Inside Airbnb
listings.csv.gzcalendar.csv.gzreviews.csv.gzneighbourhoods.csv- 原始数据摄入:将Inside Airbnb的CSV/GZIP文件加载到Snowflake内部阶段
- 分层转换:实现staging(清洗/类型转换)、intermediate(关联/增强)和mart(维度/事实)层
- 增量建模:对事实表使用Snowflake合并策略
- 数据质量:通过通用和自定义dbt测试验证唯一性、关联关系和业务规则
- 分析仪表板:Streamlit应用查询mart层数据,展示社区和房源的表现
Installation
安装步骤
bash
undefinedbash
undefinedClone 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.txt
undefinedgit 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.txt
undefinedConfiguration
配置说明
1. Snowflake Credentials
1. Snowflake凭证
Create local-only credential files (ignored by git):
bash
cp profiles.yml.example profiles.yml
cp config/local_credentials.example.json config/local_credentials.jsonprofiles.ymlyaml
airbnb_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.jsonjson
{
"account": "YOUR_ACCOUNT",
"user": "YOUR_USER",
"password": "YOUR_PASSWORD",
"role": "YOUR_ROLE",
"warehouse": "COMPUTE_WH",
"database": "AIRBNB_DB",
"schema": "ANALYTICS"
}Use environment variables in production:
bash
export SNOWFLAKE_PASSWORD="your_password"创建本地凭证文件(会被git忽略):
bash
cp profiles.yml.example profiles.yml
cp config/local_credentials.example.json config/local_credentials.jsonprofiles.ymlyaml
airbnb_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.jsonjson
{
"account": "YOUR_ACCOUNT",
"user": "YOUR_USER",
"password": "YOUR_PASSWORD",
"role": "YOUR_ROLE",
"warehouse": "COMPUTE_WH",
"database": "AIRBNB_DB",
"schema": "ANALYTICS"
}生产环境使用环境变量:
bash
export SNOWFLAKE_PASSWORD="your_password"2. Download Inside Airbnb Data
2. 下载Inside Airbnb数据
Place raw files in :
data/raw/text
data/raw/listings.csv.gz
data/raw/calendar.csv.gz
data/raw/reviews.csv.gz
data/raw/neighbourhoods.csvRecommended dataset: New York City from Inside Airbnb.
将原始文件放置在目录下:
data/raw/text
data/raw/listings.csv.gz
data/raw/calendar.csv.gz
data/raw/reviews.csv.gz
data/raw/neighbourhoods.csv推荐数据集:来自Inside Airbnb的纽约市数据。
Loading Raw Data
加载原始数据
The Python loader creates Snowflake objects and stages data:
bash
python scripts/load_inside_airbnb_to_snowflake.pyWhat it does:
- Executes to create database, schemas, and stage
setup/snowflake_setup.sql - Uploads raw files to
INSIDE_AIRBNB_STAGE - Creates raw tables with headers from CSV files
- Copies staged data into schema tables
RAW
Key loader code patterns:
python
import snowflake.connector
import jsonPython加载器会创建Snowflake对象并将数据上传到阶段:
bash
python scripts/load_inside_airbnb_to_snowflake.py执行流程:
- 执行创建数据库、模式和阶段
setup/snowflake_setup.sql - 将原始文件上传到
INSIDE_AIRBNB_STAGE - 根据CSV文件表头创建原始表
- 将阶段中的数据复制到模式的表中
RAW
加载器核心代码示例:
python
import snowflake.connector
import jsonLoad credentials
加载凭证
with open('config/local_credentials.json') as f:
creds = json.load(f)
with open('config/local_credentials.json') as f:
creds = json.load(f)
Connect to Snowflake
连接Snowflake
conn = snowflake.connector.connect(
account=creds['account'],
user=creds['user'],
password=creds['password'],
role=creds['role'],
warehouse=creds['warehouse']
)
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")
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 = '"')
""")
undefinedconn.cursor().execute("""
COPY INTO RAW.LISTINGS
FROM @INSIDE_AIRBNB_STAGE/listings.csv.gz
FILE_FORMAT = (TYPE = 'CSV' SKIP_HEADER = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '"')
""")
undefineddbt Model Architecture
dbt模型架构
Layer Structure
分层结构
| Layer | Path | Purpose | Example |
|---|---|---|---|
| Sources | | Define raw tables | |
| Staging | | Clean, cast, standardize | |
| Intermediate | | Joins, enrichment, business logic | |
| Marts | | Dimensions, facts, aggregates | |
| 层级 | 路径 | 用途 | 示例 |
|---|---|---|---|
| Sources | | 定义原始表 | |
| Staging | | 清洗、类型转换、标准化 | |
| Intermediate | | 关联、增强、业务逻辑实现 | |
| Marts | | 维度表、事实表、聚合表 | |
Staging Layer Example
Staging层示例
models/staging/stg_airbnb__listings.sqlsql
with 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 cleanedKey patterns:
- Use for raw table references
{{ source() }} - Cast types explicitly with
:: - Standardize column names (snake_case)
- Preserve raw columns when cleaning needed downstream
models/staging/stg_airbnb__listings.sqlsql
with 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() }} - 用显式转换数据类型
:: - 标准化列名(蛇形命名法)
- 清洗时保留后续可能需要的原始列
Intermediate Layer Example
Intermediate层示例
models/intermediate/int_airbnb__calendar_enriched.sqlsql
with 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 enrichedKey patterns:
- Use for model dependencies
{{ ref() }} - Join staging/intermediate models
- Add calculated business logic (revenue proxy)
- Keep intermediate models focused on reusable logic
models/intermediate/int_airbnb__calendar_enriched.sqlsql
with 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,
-- 收入估算:不可预订时的价格
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() }} - 关联staging/intermediate层模型
- 添加计算型业务逻辑(收入估算)
- 保持intermediate模型专注于可复用逻辑
Incremental Fact Table Example
增量事实表示例
models/marts/fct_listing_calendar.sqlsql
{{
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 %}Key patterns:
- for large fact tables
materialized='incremental' - for merge strategy (update existing, insert new)
unique_key - specifies which columns to update
merge_update_columns - filters new records only on subsequent runs
is_incremental() - Use flag to rebuild from scratch
--full-refresh
models/marts/fct_listing_calendar.sqlsql
{{
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_key - 指定需要更新的列
merge_update_columns - 在后续运行时仅过滤新记录
is_incremental() - 使用标志从头重建表
--full-refresh
Aggregate Mart Example
聚合Mart示例
models/marts/agg_neighbourhood_monthly_performance.sqlsql
with 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_monthKey patterns:
- Aggregate from lower-level marts
- Use for clean reporting metrics
round() - Group by dimensions for dashboards
models/marts/agg_neighbourhood_monthly_performance.sqlsql
with 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_month核心模式:
- 从低层级mart表聚合数据
- 使用生成整洁的报告指标
round() - 按维度分组以支持仪表板展示
dbt Commands
dbt命令
bash
undefinedbash
undefinedTest connection
测试连接
dbt debug --profiles-dir .
dbt debug --profiles-dir .
Run all models
运行所有模型
dbt run --profiles-dir .
dbt run --profiles-dir .
Run specific model and downstream dependencies
运行指定模型及其下游依赖
dbt run --select dim_listings+ --profiles-dir .
dbt run --select dim_listings+ --profiles-dir .
Run incremental models with full refresh
全量刷新运行增量模型
dbt run --full-refresh --select fct_listing_calendar+ --profiles-dir .
dbt run --full-refresh --select fct_listing_calendar+ --profiles-dir .
Run tests
运行测试
dbt test --profiles-dir .
dbt test --profiles-dir .
Test specific model
测试指定模型
dbt test --select stg_airbnb__listings --profiles-dir .
dbt test --select stg_airbnb__listings --profiles-dir .
Generate and serve documentation
生成并启动文档服务
dbt docs generate --profiles-dir .
dbt docs serve --profiles-dir .
**Common workflows**:
```bashdbt docs generate --profiles-dir .
dbt docs serve --profiles-dir .
**常见工作流**:
```bashNew 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 .
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 .
undefineddbt run --select +fct_reviews --profiles-dir . # 运行模型及其上游依赖
dbt test --select fct_reviews --profiles-dir .
undefinedData Quality Tests
数据质量测试
Generic Tests in Schema Files
Schema文件中的通用测试
models/staging/schema.ymlyaml
version: 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_idmodels/staging/schema.ymlyaml
version: 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_idSingular Tests
自定义测试
tests/no_duplicate_listing_dates.sqlsql
-- 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(*) > 1Key patterns:
- Generic tests in for standard validations
schema.yml - Singular tests in for custom business rules
tests/ - Tests return records that FAIL the condition
- Use package for advanced tests
dbt_utils
Install dbt packages ():
packages.ymlyaml
packages:
- package: dbt-labs/dbt_utils
version: 1.1.1bash
dbt deps --profiles-dir .tests/no_duplicate_listing_dates.sqlsql
-- 测试事实表中是否存在重复的房源-日期组合
select
listing_id,
calendar_date,
count(*) as record_count
from {{ ref('fct_listing_calendar') }}
group by listing_id, calendar_date
having count(*) > 1核心模式:
- 在中使用通用测试进行标准验证
schema.yml - 在目录中使用自定义测试实现业务规则校验
tests/ - 测试返回不符合条件的记录
- 使用包实现高级测试
dbt_utils
安装dbt包():
packages.ymlyaml
packages:
- package: dbt-labs/dbt_utils
version: 1.1.1bash
dbt deps --profiles-dir .Streamlit Dashboard
Streamlit仪表板
dashboard/streamlit_app.pypython
import streamlit as st
import snowflake.connector
import pandas as pd
import jsondashboard/streamlit_app.pypython
import streamlit as st
import snowflake.connector
import pandas as pd
import jsonLoad 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")
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'])
st.header("按估算收入排序的Top社区")
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'])
**Run dashboard**:
```bash
streamlit run dashboard/streamlit_app.pyKey patterns:
- Use for connection pooling
@st.cache_resource - Query marts directly for performance
- Filter to latest snapshot with
MAX(year_month) - Keep credentials in separate JSON file
st.header("按房源类型划分的平均价格")
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'])
**启动仪表板**:
```bash
streamlit run dashboard/streamlit_app.py核心模式:
- 使用实现连接池
@st.cache_resource - 直接查询mart层以提升性能
- 使用过滤最新数据快照
MAX(year_month) - 将凭证保存在独立的JSON文件中
Common Patterns
通用模式
Adding a New Staging Model
添加新的Staging模型
- Define source in :
models/staging/sources.yml
yaml
sources:
- name: airbnb_raw
database: AIRBNB_DB
schema: RAW
tables:
- name: new_table- Create staging model :
models/staging/stg_airbnb__new_table.sql
sql
with 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 cleaned- Add tests in :
models/staging/schema.yml
yaml
models:
- name: stg_airbnb__new_table
columns:
- name: record_id
tests:
- unique
- not_null- 在中定义数据源:
models/staging/sources.yml
yaml
sources:
- name: airbnb_raw
database: AIRBNB_DB
schema: RAW
tables:
- name: new_table- 创建Staging模型:
models/staging/stg_airbnb__new_table.sql
sql
with 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 cleaned- 在中添加测试:
models/staging/schema.yml
yaml
models:
- name: stg_airbnb__new_table
columns:
- name: record_id
tests:
- unique
- not_nullCreating a Dimension Table
创建维度表
models/marts/dim_hosts.sqlsql
with 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_aggKey patterns:
- Aggregate from enriched intermediate layer
- Use to select representative values
max() - Include business metrics (counts, averages)
models/marts/dim_hosts.sqlsql
with 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_agg核心模式:
- 从增强后的intermediate层聚合数据
- 使用选择代表性值
max() - 包含业务指标(计数、平均值)
Monthly Aggregation Pattern
月度聚合模式
sql
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')Key patterns:
- Use for month grouping in Snowflake
to_char(date, 'YYYY-MM') - Calculate rates with
avg(case when condition then 1.0 else 0.0 end) - Aggregate revenue as sum, prices as average
sql
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')核心模式:
- 在Snowflake中使用进行月度分组
to_char(date, 'YYYY-MM') - 使用计算比率
avg(case when condition then 1.0 else 0.0 end) - 收入按总和聚合,价格按平均值聚合
Troubleshooting
故障排除
dbt Connection Issues
dbt连接问题
Error:
Database Error in model [...] (...) 250001 (08001): Failed to connect to DBSolution:
- Verify has correct Snowflake account identifier
profiles.yml - Test connection:
dbt debug --profiles-dir . - Check Snowflake credentials and network access
- Ensure warehouse is running
错误:
Database Error in model [...] (...) 250001 (08001): Failed to connect to DB解决方案:
- 验证中的Snowflake账户标识符是否正确
profiles.yml - 测试连接:
dbt debug --profiles-dir . - 检查Snowflake凭证和网络访问权限
- 确保计算仓库处于运行状态
Incremental Model Not Updating
增量模型未更新
Error: New data not appearing in incremental fact table
Solution:
bash
undefined错误: 新数据未出现在增量事实表中
解决方案:
bash
undefinedForce full rebuild
强制全量重建
dbt run --full-refresh --select fct_listing_calendar --profiles-dir .
Check `unique_key` matches grain in config:
```sql
{{
config(
unique_key=['listing_id', 'calendar_date'] -- Must match table grain
)
}}dbt run --full-refresh --select fct_listing_calendar --profiles-dir .
检查配置中的`unique_key`是否与表粒度匹配:
```sql
{{
config(
unique_key=['listing_id', 'calendar_date'] -- 必须与表粒度匹配
)
}}Test Failures on Price Data
价格数据测试失败
Error: fails on price column
dbt_utils.expression_is_trueSolution: Raw price data may contain non-numeric values or currency symbols.
Clean in staging layer:
sql
-- Remove $ and commas, cast to numeric
replace(replace(price, '$', ''), ',', '')::decimal(10,2) as price错误: 在价格列上测试失败
dbt_utils.expression_is_true解决方案: 原始价格数据可能包含非数值或货币符号。
在Staging层进行清洗:
sql
-- 移除$和逗号,转换为数值类型
replace(replace(price, '$', ''), ',', '')::decimal(10,2) as priceStreamlit Connection Timeout
Streamlit连接超时
Error:
OperationalError: 250001 (08001): Failed to connectSolution:
- Check credentials
config/local_credentials.json - Verify Snowflake warehouse is running
- Add timeout config:
python
conn = snowflake.connector.connect(
...,
login_timeout=30,
network_timeout=30
)错误:
OperationalError: 250001 (08001): Failed to connect解决方案:
- 检查中的凭证
config/local_credentials.json - 验证Snowflake计算仓库是否运行
- 添加超时配置:
python
conn = snowflake.connector.connect(
...,
login_timeout=30,
network_timeout=30
)Missing Stage Files
阶段文件缺失
Error: when running loader script
File not foundSolution:
- Verify raw files exist in
data/raw/ - Check file names match loader script expectations
- Ensure files are compressed () where expected
.gz
错误: 运行加载器脚本时出现
File not found解决方案:
- 验证原始文件是否存在于目录
data/raw/ - 检查文件名是否与加载器脚本预期一致
- 确保文件按预期压缩(格式)
.gz
dbt Model Dependency Errors
dbt模型依赖错误
Error:
Compilation Error: Model 'X' depends on a node named 'Y' which was not foundSolution:
- Check matches actual model file name
{{ ref('model_name') }} - Run to install packages
dbt deps --profiles-dir . - Verify model exists in directory
models/
错误:
Compilation Error: Model 'X' depends on a node named 'Y' which was not found解决方案:
- 检查是否与实际模型文件名匹配
{{ ref('model_name') }} - 运行安装依赖包
dbt deps --profiles-dir . - 验证模型是否存在于目录
models/
Environment Variables for Production
生产环境环境变量
Use environment variables instead of local credential files:
dbt :
profiles.ymlyaml
airbnb_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: 4Streamlit connection:
python
import 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'
)使用环境变量替代本地凭证文件:
dbt :
profiles.ymlyaml
airbnb_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: 4Streamlit连接:
python
import 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'
)Project Resources
项目资源
- GitHub: analyticsdurgesh/Snowflake_DBT_Project
- Inside Airbnb: insideairbnb.com
- dbt Docs: docs.getdbt.com
- Snowflake Docs: docs.snowflake.com
- GitHub: analyticsdurgesh/Snowflake_DBT_Project
- Inside Airbnb: insideairbnb.com
- dbt文档: docs.getdbt.com
- Snowflake文档: docs.snowflake.com