Snowflake MigrationClickHouse Workshops

Snowflake 上的 Superset

构建源端的三个运营看板:连接、数据集、图表与组装。

本指南带你在 http://localhost:8088 (admin / admin)的 Apache Superset 中创建全部三个看板。

操作顺序:

  1. 启动 Superset 并注册 Snowflake 连接
  2. 创建所有数据集(保存为命名数据集的 SQL 查询)
  3. 构建图表并组装看板

第 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.

注册数据库连接(手动方式)

如果你更愿意通过界面注册:

  1. 前往 Settings → Database Connections → + Database
  2. 选择 Snowflake
  3. 填写 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
  1. 设置 Display Name:NYC Taxi — Snowflake (Source)
  2. 在 Advanced → SQL Lab 下:启用 Allow this database to be explored 和 Allow DML
  3. 点击 Test Connection → 应显示 "Connection looks good!"
  4. 点击 Connect

第 2 步:创建所有数据集

所有图表都使用虚拟数据集,即保存为命名数据集的 SQL 查询。

每个数据集的创建方法:

  1. 前往 Datasets → + Dataset
  2. 选择数据库:NYC Taxi — Snowflake (Source)
  3. 点击 Create dataset from SQL query,并粘贴下面的 SQL
  4. 用给出的名称保存
  5. 保存之后:前往 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 DESC

ops_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 365

exec_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_borough

exec_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 1

dqa_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 DESC

dqa_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 DESC

dqa_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 的看板。

创建看板:

  1. Dashboards → + Dashboard
  2. 标题:Operations Command Center
  3. 自动刷新:每 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 上需要改写。

创建看板:

  1. Dashboards → + Dashboard
  2. 标题:Executive Weekly Report
  3. 自动刷新: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 性能基准测试的基线。

创建看板:

  1. Dashboards → + Dashboard
  2. 标题:Driver & Quality Analytics
  3. 自动刷新: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 步:验证

  1. 打开每个看板,确认所有图表都能无错加载
  2. 对于看板 3,在 Snowflake UI → Activity → Query History 中记下 dqa_rating_dist 的查询执行时间,把它保存为你的迁移基准

Superset 图表显示 Snowflake 数据集中的司机评分分布,集中在 4.0 到 5.0 之间

第 5 步:导出以便复用

看板完成后,把它们导出,这样以后运行时可以自动导入:

  1. 打开每个看板 → ··· → Export(保存为 .zip)
  2. 把文件放到 superset/dashboards/:
    • 01_operations_command_center.zip
    • 02_executive_weekly_report.zip
    • 03_driver_quality_analytics.zip
  3. 重新运行 ./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 历史。

本页内容

第 1 步:启动 Superset 并连接 Snowflake启动 Superset注册数据库连接(自动)注册数据库连接(手动方式)第 2 步:创建所有数据集看板 1 的数据集ops_hourly_revenue:按行政区的小时级营收ops_zone_agg:zone 聚合ops_payment_split:支付方式占比看板 2 的数据集exec_rolling_avg:滚动 7 天均值exec_top_trips:每个行政区价值最高的 10 次行程exec_surge:动态加价的影响看板 3 的数据集dqa_rating_dist:司机评分分布dqa_vehicle:按车辆类型的营收dqa_traffic:交通拥堵程度与行程时长的关系dqa_platform:App 平台趋势第 3 步:构建看板看板 1:Operations Command Center图表 1:每小时行程数(Line chart)图表 2:按行政区的营收(Bar chart)图表 3:支付方式占比(Pie chart)图表 4:总行程数(Big Number)图表 5:行政区表现汇总(Table)看板 2:Executive Weekly Report图表 6:滚动 7 天营收趋势(Line chart)图表 7a:每日行程量(Big Number)图表 7b:滚动 7 天平均里程(Line chart)图表 8:每个行政区的前 10 次行程(Table)图表 9:动态加价拆解(Mixed chart)图表 10:加价分布(Pie chart)看板 3:Driver & Quality Analytics图表 11:按司机评分的行程数(Bar chart)图表 12:按评分的平均车费(Line chart)图表 13:按车辆类型的营收(横向条形图)图表 14:交通拥堵程度的影响(Bar chart)图表 15:按 App 平台的每日行程数(Line chart)图表 16:按平台的加价情况(Table)第 4 步:验证第 5 步:导出以便复用
ZH