Snowflake 上的 Superset
构建源端的三个运营看板:连接、数据集、图表与组装。
本指南带你在 http://localhost:8088 (admin / admin)的 Apache Superset 中创建全部三个看板。
操作顺序:
- 启动 Superset 并注册 Snowflake 连接
- 创建所有数据集(保存为命名数据集的 SQL 查询)
- 构建图表并组装看板
第 1 步:启动 Superset 并连接 Snowflake
启动 Superset
这个 Superset 镜像经过定制,包含了 Snowflake 与 ClickHouse 驱动。首次运行时使用 --build,让 Docker 构建它:
cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake/superset"
source ../.env
docker compose up -d --build注册数据库连接(自动)
在 superset/ 目录下运行:
source ../.env && ./init_superset.sh脚本会等待 Superset 就绪,然后自动注册 NYC Taxi — Snowflake (Source)。预期输出:
>>> Superset is up.
>>> Authenticated.
>>> CSRF token obtained.
>>> Registering: NYC Taxi — Snowflake (Source)
Registered successfully.注册数据库连接(手动方式)
如果你更愿意通过界面注册:
- 前往 Settings → Database Connections → + Database
- 选择 Snowflake
- 填写 SQLAlchemy URI,请对密码中的任何特殊字符做 URL 编码(
#→%23、!→%21、@→%40等):
snowflake://<USER>:<URL_ENCODED_PASSWORD>@<SNOWFLAKE_ORG>-<SNOWFLAKE_ACCOUNT>/NYC_TAXI_DB/ANALYTICS?warehouse=ANALYTICS_WH&role=ANALYST_ROLE- 设置 Display Name:
NYC Taxi — Snowflake (Source) - 在 Advanced → SQL Lab 下:启用 Allow this database to be explored 和 Allow DML
- 点击 Test Connection → 应显示 "Connection looks good!"
- 点击 Connect
第 2 步:创建所有数据集
所有图表都使用虚拟数据集,即保存为命名数据集的 SQL 查询。
每个数据集的创建方法:
- 前往 Datasets → + Dataset
- 选择数据库:
NYC Taxi — Snowflake (Source) - 点击 Create dataset from SQL query,并粘贴下面的 SQL
- 用给出的名称保存
- 保存之后:前往 Datasets → 铅笔图标 → Columns 标签页 → "Sync columns from source" → Save。跳过这一步,图表构建器会显示 0 列。
看板 1 的数据集
ops_hourly_revenue:按行政区的小时级营收
Schema:ANALYTICS
SELECT
DATE_TRUNC('hour', pickup_at) AS hour_bucket,
pickup_borough,
COUNT(*) AS trip_count,
SUM(total_amount_usd) AS total_revenue,
AVG(tip_amount_usd / NULLIF(fare_amount_usd, 0)) AS avg_tip_rate,
AVG(trip_distance_miles) AS avg_distance_miles
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at >= DATEADD('day', -7, CURRENT_TIMESTAMP())
AND pickup_borough IS NOT NULL
GROUP BY 1, 2
ORDER BY 1 DESC, total_revenue DESCops_zone_agg:zone 聚合
Schema:ANALYTICS
SELECT
hour_bucket,
zone_id,
trips,
revenue,
avg_distance
FROM ANALYTICS.AGG_HOURLY_ZONE_TRIPS
WHERE hour_bucket >= DATEADD('day', -7, CURRENT_TIMESTAMP())ops_payment_split:支付方式占比
Schema:ANALYTICS
SELECT
payment_type,
COUNT(*) AS trip_count,
SUM(total_amount_usd) AS total_revenue
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1看板 2 的数据集
exec_rolling_avg:滚动 7 天均值
Schema:ANALYTICS
SELECT
pickup_at::DATE AS trip_date,
COUNT(*) AS daily_trip_count,
AVG(trip_distance_miles) AS daily_avg_distance,
AVG(AVG(trip_distance_miles)) OVER (
ORDER BY pickup_at::DATE
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_7d_avg_distance,
SUM(total_amount_usd) AS daily_revenue,
SUM(SUM(total_amount_usd)) OVER (
ORDER BY pickup_at::DATE
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_7d_revenue
FROM ANALYTICS.FACT_TRIPS
GROUP BY 1
ORDER BY 1 DESC
LIMIT 365exec_top_trips:每个行政区价值最高的 10 次行程
Schema:ANALYTICS
使用了 Snowflake 的
QUALIFY,这是一个关键迁移挑战。改写到 ClickHouse 需要用子查询。
SELECT
trip_id,
pickup_at,
pickup_borough,
total_amount_usd,
tip_amount_usd,
trip_distance_miles,
ROW_NUMBER() OVER (
PARTITION BY pickup_borough
ORDER BY total_amount_usd DESC
) AS rank_in_borough
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at::DATE = CURRENT_DATE() - 1
QUALIFY rank_in_borough <= 10
ORDER BY pickup_borough, rank_in_boroughexec_surge:动态加价的影响
Schema:ANALYTICS
SELECT
CASE
WHEN surge_multiplier >= 2.0 THEN 'High Surge (2x+)'
WHEN surge_multiplier >= 1.5 THEN 'Medium Surge (1.5–2x)'
WHEN surge_multiplier > 1.0 THEN 'Low Surge (1–1.5x)'
ELSE 'No Surge (1x)'
END AS surge_category,
COUNT(*) AS trip_count,
ROUND(AVG(total_amount_usd), 2) AS avg_total_fare,
ROUND(AVG(fare_amount_usd), 2) AS avg_base_fare,
ROUND(AVG(surge_multiplier), 2) AS avg_surge
FROM ANALYTICS.FACT_TRIPS
WHERE surge_multiplier IS NOT NULL
GROUP BY 1
ORDER BY avg_surge DESC看板 3 的数据集
dqa_rating_dist:司机评分分布
Schema:RAW ← 创建该数据集时记得改成这个
通过 VARIANT 冒号路径语法直接查询
RAW.TRIPS_RAW。这是刻意设置的慢查询,也是 ClickHouse 基准测试的对标目标。
SELECT
ROUND(TRIP_METADATA:driver.rating::FLOAT, 1) AS rating_bucket,
COUNT(*) AS trip_count,
ROUND(AVG(TOTAL_AMOUNT), 2) AS avg_fare,
ROUND(AVG(DATEDIFF('minute', PICKUP_DATETIME, DROPOFF_DATETIME)), 1) AS avg_duration_minutes
FROM RAW.TRIPS_RAW
WHERE TRIP_METADATA:driver IS NOT NULL
AND TRIP_METADATA:driver.rating IS NOT NULL
GROUP BY 1
ORDER BY 1dqa_vehicle:按车辆类型的营收
Schema:ANALYTICS
SELECT
vehicle_type,
COUNT(*) AS trip_count,
SUM(total_amount_usd) AS total_revenue,
AVG(total_amount_usd) AS avg_fare,
AVG(trip_distance_miles) AS avg_distance
FROM ANALYTICS.FACT_TRIPS
WHERE vehicle_type IS NOT NULL
GROUP BY 1
ORDER BY total_revenue DESCdqa_traffic:交通拥堵程度与行程时长的关系
Schema:ANALYTICS
SELECT
traffic_level,
COUNT(*) AS trip_count,
AVG(duration_minutes) AS avg_duration_minutes,
AVG(trip_distance_miles) AS avg_distance_miles,
AVG(total_amount_usd) AS avg_fare
FROM ANALYTICS.FACT_TRIPS
WHERE traffic_level IS NOT NULL
GROUP BY 1
ORDER BY avg_duration_minutes DESCdqa_platform:App 平台趋势
Schema:ANALYTICS
SELECT
pickup_at::DATE AS trip_date,
app_platform,
COUNT(*) AS trip_count,
AVG(surge_multiplier) AS avg_surge
FROM ANALYTICS.FACT_TRIPS
WHERE app_platform IS NOT NULL
AND pickup_at >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY 1 DESC第 3 步:构建看板
10 个数据集现在都已就绪。接下来创建图表并把它们加入看板。
看板 1:Operations Command Center
目的: 展示最近 7 天的实时运营视图。这是第 2 部分中合作方最先改指向 ClickHouse 的看板。
创建看板:
- Dashboards → + Dashboard
- 标题:
Operations Command Center - 自动刷新:每 15 分钟(
···→ Edit dashboard → Auto-refresh)
图表 1:每小时行程数(Line chart)
- Chart type:Line Chart
- Dataset:
ops_hourly_revenue - X-axis:
hour_bucket - Metrics:
SUM(trip_count) - Series:
pickup_borough - Title:
Trips per Hour — Last 7 Days
图表 2:按行政区的营收(Bar chart)
- Chart type:Bar Chart
- Dataset:
ops_hourly_revenue - X-axis:
pickup_borough - Metrics:
SUM(total_revenue) - Sort:按指标降序
- Title:
Total Revenue by Borough — Last 7 Days
图表 3:支付方式占比(Pie chart)
- Chart type:Pie Chart
- Dataset:
ops_payment_split - Dimension:
payment_type - Metric:
SUM(trip_count) - Show labels:开启
- Title:
Trip Count by Payment Type
图表 4:总行程数(Big Number)
- Chart type:Big Number with Trendline
- Dataset:
ops_hourly_revenue - Metric:
SUM(trip_count) - Title:
Total Trips (Last 7 Days)
图表 5:行政区表现汇总(Table)
- Chart type:Table
- Dataset:
ops_hourly_revenue - Columns:
pickup_borough、SUM(trip_count)、SUM(total_revenue)、AVG(avg_tip_rate) - Row limit:10
- Sort:
SUM(total_revenue)降序 - Title:
Borough Performance Summary
布局:
[ Total Trips — Big Number ] [ Total Revenue — Big Number (add 2nd) ]
[ Trips per Hour — Line chart (full width) ]
[ Revenue by Borough — Bar ] [ Payment Type Split — Pie ]
[ Borough Performance — Table (full width) ]看板 2:Executive Weekly Report
目的: 面向每周业务复盘的战略视图。它展示了窗口函数以及 Snowflake 专属语法(QUALIFY),这些在 ClickHouse 上需要改写。
创建看板:
- Dashboards → + Dashboard
- 标题:
Executive Weekly Report - 自动刷新:1 小时
图表 6:滚动 7 天营收趋势(Line chart)
- Chart type:Line Chart
- Dataset:
exec_rolling_avg - X-axis:
trip_date - Metrics:
MAX(daily_revenue)、MAX(rolling_7d_revenue) - Title:
Daily Revenue with 7-Day Rolling Average
图表 7a:每日行程量(Big Number)
- Chart type:Big Number with Trendline
- Dataset:
exec_rolling_avg - Metric:
MAX(daily_trip_count) - Title:
Daily Trip Volume (Last Year)
图表 7b:滚动 7 天平均里程(Line chart)
- Chart type:Line Chart
- Dataset:
exec_rolling_avg - X-axis:
trip_date - Metrics:
MAX(rolling_7d_avg_distance) - Title:
Rolling 7-Day Average Distance (miles)
图表 8:每个行政区的前 10 次行程(Table)
- Chart type:Table
- Dataset:
exec_top_trips - Query Mode:RAW RECORDS ← 重要:该数据集使用了
QUALIFY,因此 Superset 不能再做一次聚合 - Columns:
pickup_borough、rank_in_borough、total_amount_usd、tip_amount_usd、trip_distance_miles、pickup_at - Sort By:
total_amount_usd降序 - Row limit:60
- Title:
Top 10 Trips per Borough — Yesterday - 说明:使用了
QUALIFY,这是 Snowflake 专属语法,迁到 ClickHouse 必须改写为子查询
图表 9:动态加价拆解(Mixed chart)
- Chart type:Mixed Chart ← 用这个,不要用 Bar Chart;Bar Chart 不支持副坐标轴
- Dataset:
exec_surge - X-axis:
surge_category - Query A,Bar:指标
SUM(trip_count),标签Trip Count - Query B,Line:指标
MAX(avg_total_fare),标签Avg Total Fare,Y 轴:Right - Sort:
SUM(trip_count)降序 - Title:
Trip Volume and Average Fare by Surge Category
图表 10:加价分布(Pie chart)
- Chart type:Pie Chart
- Dataset:
exec_surge - Dimension:
surge_category - Metric:
SUM(trip_count) - Title:
Surge Pricing Distribution
布局:
[ Rolling Revenue — Line chart (full width) ]
[ Daily Trip Volume — Big Number (50%) ] [ Avg Distance — Line (50%) ]
[ Top 10 Trips — Table (60%) ] [ Surge Distribution — Pie (40%) ]
[ Surge Breakdown — Bar chart (full width) ]看板 3:Driver & Quality Analytics
目的: 深入分析司机表现与行程质量。它是刻意做成最慢的看板,通过 VARIANT 访问直接查询 RAW.TRIPS_RAW。请在这里记录查询耗时,作为第 2 部分 ClickHouse 性能基准测试的基线。
创建看板:
- Dashboards → + Dashboard
- 标题:
Driver & Quality Analytics - 自动刷新:1 小时
图表 11:按司机评分的行程数(Bar chart)
- Chart type:Bar Chart
- Dataset:
dqa_rating_dist - X-axis:
rating_bucket - Metrics:
SUM(trip_count) - Title:
Trip Count by Driver Rating - 说明:以 VARIANT 访问扫描
RAW.TRIPS_RAW,注意对比它与 ClickHouse 的查询耗时
图表 12:按评分的平均车费(Line chart)
- Chart type:Line Chart
- Dataset:
dqa_rating_dist - X-axis:
rating_bucket - Metrics:
MAX(avg_fare) - Title:
Average Fare by Driver Rating
图表 13:按车辆类型的营收(横向条形图)
- Chart type:Bar Chart(horizontal)
- Dataset:
dqa_vehicle - X-axis:
vehicle_type - Metrics:
SUM(total_revenue)、SUM(trip_count)(副坐标轴) - Title:
Revenue and Trip Count by Vehicle Type
图表 14:交通拥堵程度的影响(Bar chart)
- Chart type:Bar Chart
- Dataset:
dqa_traffic - X-axis:
traffic_level - Metrics:
MAX(avg_duration_minutes)、MAX(avg_distance_miles)(副坐标轴) - Title:
Average Trip Duration and Distance by Traffic Level
图表 15:按 App 平台的每日行程数(Line chart)
- Chart type:Line Chart
- Dataset:
dqa_platform - X-axis:
trip_date - Metrics:
SUM(trip_count) - Series:
app_platform - Title:
Daily Trips by App Platform — Last 30 Days
图表 16:按平台的加价情况(Table)
- Chart type:Table
- Dataset:
dqa_platform - Columns:
app_platform、SUM(trip_count)、AVG(avg_surge) - Row limit:10
- Title:
Surge by Platform
布局:
[ Trip Count by Rating — Bar ] [ Avg Fare by Rating — Line ]
[ Revenue by Vehicle Type — Horizontal bar (full width) ]
[ Traffic Level Impact — Bar (50%) ] [ Surge by Platform — Table (50%)]
[ Daily Trips by Platform — Line chart (full width) ]第 4 步:验证
- 打开每个看板,确认所有图表都能无错加载
- 对于看板 3,在 Snowflake UI → Activity → Query History 中记下
dqa_rating_dist的查询执行时间,把它保存为你的迁移基准

第 5 步:导出以便复用
看板完成后,把它们导出,这样以后运行时可以自动导入:
- 打开每个看板 →
···→ Export(保存为.zip) - 把文件放到
superset/dashboards/:01_operations_command_center.zip02_executive_weekly_report.zip03_driver_quality_analytics.zip
- 重新运行
./init_superset.sh,以后搭建时它会自动导入这些文件
重要,已提交 ZIP 中的占位凭据。
每个已提交的
*.zip都在databases/*.yaml中对数据库连接做了脱敏:sqlalchemy_uri: snowflake://LAB_USER:XXXXXXXXXX@MYORG-MYACCOUNT/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH
- 自动导入(
./init_superset.sh),开箱可用。脚本会先用.env中的信息注册真实的 Snowflake 连接,再在每次导入之后重新应用正确的 URI(见init_superset.sh中的_update_db),因此占位值会被你的真实凭据覆盖。- 通过 Superset 界面手动导入,导入出的数据库会带着占位 URI 创建,无法连接。导入后前往 Settings → Database Connections → Edit 编辑该条目,把
sqlalchemy_uri替换为你真实的 Snowflake URI(例如snowflake://<USER>:<PASSWORD>@<ORG>-<ACCOUNT>/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH)。- 重新导出你自己的看板,导出时 Superset 会把你的账号定位符和用户名写进
databases/*.yaml。在提交重新导出的 ZIP 之前,请把这些值改回MYORG-MYACCOUNT/LAB_USER,以免你的账号标识泄漏进 git 历史。