Snowflake MigrationClickHouse Workshops

Superset บน Snowflake

การสร้าง dashboard ปฏิบัติการฝั่งต้นทางทั้งสามชุด: การเชื่อมต่อ, datasets, charts และการประกอบร่าง

คู่มือนี้พาคุณสร้าง dashboard ทั้งสามชุดใน Apache Superset ที่ http://localhost:8088 (admin / admin)

ลำดับการทำงาน:

  1. เริ่ม Superset และลงทะเบียนการเชื่อมต่อ Snowflake
  2. สร้าง dataset ทั้งหมด (query SQL ที่บันทึกไว้เป็น dataset ที่มีชื่อ)
  3. สร้าง chart และประกอบเป็น dashboard

ขั้นที่ 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.

ลงทะเบียนการเชื่อมต่อฐานข้อมูล (ทางเลือกแบบทำมือ)

ถ้าคุณต้องการลงทะเบียนผ่าน UI:

  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: สร้าง Dataset ทั้งหมด

chart ทั้งหมดใช้ virtual dataset — query SQL ที่บันทึกไว้เป็น dataset ที่มีชื่อ

วิธีสร้างแต่ละ dataset:

  1. ไปที่ Datasets → + Dataset
  2. เลือกฐานข้อมูล: NYC Taxi — Snowflake (Source)
  3. คลิก Create dataset from SQL query แล้ววาง SQL ด้านล่าง
  4. บันทึกด้วยชื่อที่ระบุไว้
  5. หลังบันทึก: ไปที่ Datasets → ไอคอนดินสอ → แท็บ Columns → "Sync columns from source" → Save ถ้าไม่ทำขั้นนี้ ตัวสร้าง chart จะแสดง 0 คอลัมน์

Dataset ของ Dashboard 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 — ค่า aggregate ระดับโซน

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

Dataset ของ Dashboard 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

ใช้ QUALIFY ของ Snowflake — ความท้าทายสำคัญของการย้ายระบบ การเขียนใหม่บน ClickHouse ต้องใช้ subquery

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

Dataset ของ Dashboard 3

dqa_rating_dist — การกระจายคะแนนคนขับ

Schema: RAW ← เปลี่ยนค่านี้เมื่อสร้าง dataset

Query RAW.TRIPS_RAW โดยตรงผ่านไวยากรณ์ colon-path ของ VARIANT นี่คือ query ที่ตั้งใจให้ช้า — เป้าหมายของ benchmark กับ 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 — แนวโน้มตามแพลตฟอร์มแอป

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: สร้าง Dashboard

ตอนนี้ dataset ทั้ง 10 ชุดพร้อมแล้ว สร้าง chart และเพิ่มลงใน dashboard


Dashboard 1: Operations Command Center

วัตถุประสงค์: มุมมองปฏิบัติการแบบเรียลไทม์ที่แสดง 7 วันล่าสุด นี่คือ dashboard ชุดแรกที่พาร์ตเนอร์จะชี้ไปที่ ClickHouse ใน Part 2

สร้าง dashboard:

  1. Dashboards → + Dashboard
  2. Title: Operations Command Center
  3. Auto-refresh: ทุก 15 นาที (··· → Edit dashboard → Auto-refresh)

Chart 1: Trips per Hour (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

Chart 2: Revenue by Borough (Bar chart)

  • Chart type: Bar Chart
  • Dataset: ops_hourly_revenue
  • X-axis: pickup_borough
  • Metrics: SUM(total_revenue)
  • Sort: จากมากไปน้อยตาม metric
  • Title: Total Revenue by Borough — Last 7 Days

Chart 3: Payment Type Split (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

Chart 4: Total Trips (Big Number)

  • Chart type: Big Number with Trendline
  • Dataset: ops_hourly_revenue
  • Metric: SUM(trip_count)
  • Title: Total Trips (Last 7 Days)

Chart 5: Borough Performance Summary (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)                            ]

Dashboard 2: Executive Weekly Report

วัตถุประสงค์: มุมมองเชิงกลยุทธ์สำหรับการทบทวนธุรกิจรายสัปดาห์ แสดงตัวอย่าง window function และไวยากรณ์เฉพาะของ Snowflake (QUALIFY) ที่ต้องเขียนใหม่ใน ClickHouse

สร้าง dashboard:

  1. Dashboards → + Dashboard
  2. Title: Executive Weekly Report
  3. Auto-refresh: 1 ชั่วโมง

Chart 6: Rolling 7-Day Revenue Trend (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

Chart 7a: Daily Trip Volume (Big Number)

  • Chart type: Big Number with Trendline
  • Dataset: exec_rolling_avg
  • Metric: MAX(daily_trip_count)
  • Title: Daily Trip Volume (Last Year)

Chart 7b: Rolling 7-Day Average Distance (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)

Chart 8: Top 10 Trips per Borough (Table)

  • Chart type: Table
  • Dataset: exec_top_trips
  • Query Mode: RAW RECORDS ← สำคัญ: dataset นี้ใช้ QUALIFY ดังนั้น Superset ต้องไม่ aggregate ซ้ำ
  • 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 ต้องเขียนใหม่เป็น subquery สำหรับ ClickHouse

Chart 9: Surge Pricing Breakdown (Mixed chart)

  • Chart type: Mixed Chart ← ใช้ตัวนี้ ไม่ใช่ Bar Chart เพราะ Bar Chart ไม่รองรับแกนรอง
  • Dataset: exec_surge
  • X-axis: surge_category
  • Query A — Bar: metric SUM(trip_count), label Trip Count
  • Query B — Line: metric MAX(avg_total_fare), label Avg Total Fare, Y-axis: Right
  • Sort: SUM(trip_count) จากมากไปน้อย
  • Title: Trip Volume and Average Fare by Surge Category

Chart 10: Surge Distribution (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)                               ]

Dashboard 3: Driver & Quality Analytics

วัตถุประสงค์: เจาะลึกประสิทธิภาพคนขับและคุณภาพของทริป ตั้งใจให้เป็น dashboard ที่ช้าที่สุด — query RAW.TRIPS_RAW โดยตรงผ่านการเข้าถึง VARIANT บันทึกเวลา query ที่นี่ไว้เป็นค่าอ้างอิงพื้นฐานสำหรับ benchmark ประสิทธิภาพของ ClickHouse ใน Part 2

สร้าง dashboard:

  1. Dashboards → + Dashboard
  2. Title: Driver & Quality Analytics
  3. Auto-refresh: 1 ชั่วโมง

Chart 11: Trip Count by Driver Rating (Bar chart)

  • Chart type: Bar Chart
  • Dataset: dqa_rating_dist
  • X-axis: rating_bucket
  • Metrics: SUM(trip_count)
  • Title: Trip Count by Driver Rating
  • หมายเหตุ: สแกน RAW.TRIPS_RAW ด้วยการเข้าถึง VARIANT — สังเกตเวลา query เทียบกับ ClickHouse

Chart 12: Average Fare by Rating (Line chart)

  • Chart type: Line Chart
  • Dataset: dqa_rating_dist
  • X-axis: rating_bucket
  • Metrics: MAX(avg_fare)
  • Title: Average Fare by Driver Rating

Chart 13: Revenue by Vehicle Type (Horizontal bar)

  • Chart type: Bar Chart (แนวนอน)
  • Dataset: dqa_vehicle
  • X-axis: vehicle_type
  • Metrics: SUM(total_revenue), SUM(trip_count) (แกนรอง)
  • Title: Revenue and Trip Count by Vehicle Type

Chart 14: Traffic Level Impact (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

Chart 15: Daily Trips by App Platform (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

Chart 16: Surge by Platform (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. เปิด dashboard แต่ละชุดและยืนยันว่า chart ทั้งหมดโหลดได้โดยไม่มีข้อผิดพลาด
  2. สำหรับ Dashboard 3 ให้จดเวลาที่ใช้รัน query ของ dqa_rating_dist ใน Snowflake UI → Activity → Query History — เก็บค่านี้ไว้เป็น benchmark ของการย้ายระบบ

กราฟ Superset แสดงการกระจายคะแนนคนขับในชุดข้อมูล Snowflake โดยกระจุกอยู่ระหว่าง 4.0 ถึง 5.0

ขั้นที่ 5: ส่งออกเพื่อนำกลับมาใช้

เมื่อ dashboard เสร็จสมบูรณ์ ให้ส่งออกไว้เพื่อให้การรันครั้งถัด ๆ ไปนำเข้าได้อัตโนมัติ:

  1. เปิด dashboard แต่ละชุด → ··· → Export (บันทึกเป็น .zip)
  2. วางไฟล์ไว้ใน superset/dashboards/:
    • 01_operations_command_center.zip
    • 02_executive_weekly_report.zip
    • 03_driver_quality_analytics.zip
  3. รัน ./init_superset.sh อีกครั้ง — มันจะนำเข้าไฟล์เหล่านี้ให้อัตโนมัติในการ setup ครั้งถัดไป

สำคัญ — credential ตัวอย่างใน ZIP ที่ commit ไว้

*.zip แต่ละไฟล์ที่ commit ไว้มีการเชื่อมต่อฐานข้อมูลถูกปิดบังไว้ใน databases/*.yaml:

sqlalchemy_uri: snowflake://LAB_USER:XXXXXXXXXX@MYORG-MYACCOUNT/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH
  • การนำเข้าอัตโนมัติ (./init_superset.sh) — ใช้ได้ทันที สคริปต์จะลงทะเบียนการเชื่อมต่อ Snowflake จริงจาก .env ก่อน การนำเข้า แล้วใส่ URI ที่ถูกต้องกลับ หลัง การนำเข้าแต่ละครั้ง (ดู _update_db ใน init_superset.sh) ดังนั้นค่าตัวอย่างจะถูกเขียนทับด้วย credential จริงของคุณ
  • การนำเข้าด้วยมือผ่าน Superset UI — ฐานข้อมูลที่นำเข้าจะถูกสร้างด้วย URI ตัวอย่างและเชื่อมต่อไม่ได้ หลังนำเข้า ให้ไปที่ Settings → Database Connections → Edit รายการนั้นและแทน sqlalchemy_uri ด้วย URI ของ Snowflake จริงของคุณ (เช่น snowflake://<USER>:<PASSWORD>@<ORG>-<ACCOUNT>/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH)
  • การส่งออก dashboard ของคุณเองอีกครั้ง — Superset ฝัง account locator และชื่อผู้ใช้ของคุณลงใน databases/*.yaml ตอนส่งออก ก่อน commit ZIP ที่คุณส่งออกใหม่ ให้ปิดบังค่าเหล่านั้นกลับไปเป็น MYORG-MYACCOUNT / LAB_USER เพื่อไม่ให้ตัวระบุบัญชีของคุณรั่วเข้าไปในประวัติ git

ในหน้านี้

ขั้นที่ 1: เริ่ม Superset และเชื่อมต่อกับ Snowflakeเริ่ม Supersetลงทะเบียนการเชื่อมต่อฐานข้อมูล (แบบอัตโนมัติ)ลงทะเบียนการเชื่อมต่อฐานข้อมูล (ทางเลือกแบบทำมือ)ขั้นที่ 2: สร้าง Dataset ทั้งหมดDataset ของ Dashboard 1ops_hourly_revenue — รายได้รายชั่วโมงแยกตามเขตops_zone_agg — ค่า aggregate ระดับโซนops_payment_split — สัดส่วนประเภทการชำระเงินDataset ของ Dashboard 2exec_rolling_avg — ค่าเฉลี่ยเลื่อน 7 วันexec_top_trips — 10 ทริปมูลค่าสูงสุดต่อเขตexec_surge — ผลกระทบของราคาช่วงพีคDataset ของ Dashboard 3dqa_rating_dist — การกระจายคะแนนคนขับdqa_vehicle — รายได้ตามประเภทยานพาหนะdqa_traffic — ระดับการจราจรเทียบกับระยะเวลาเดินทางdqa_platform — แนวโน้มตามแพลตฟอร์มแอปขั้นที่ 3: สร้าง DashboardDashboard 1: Operations Command CenterChart 1: Trips per Hour (Line chart)Chart 2: Revenue by Borough (Bar chart)Chart 3: Payment Type Split (Pie chart)Chart 4: Total Trips (Big Number)Chart 5: Borough Performance Summary (Table)Dashboard 2: Executive Weekly ReportChart 6: Rolling 7-Day Revenue Trend (Line chart)Chart 7a: Daily Trip Volume (Big Number)Chart 7b: Rolling 7-Day Average Distance (Line chart)Chart 8: Top 10 Trips per Borough (Table)Chart 9: Surge Pricing Breakdown (Mixed chart)Chart 10: Surge Distribution (Pie chart)Dashboard 3: Driver & Quality AnalyticsChart 11: Trip Count by Driver Rating (Bar chart)Chart 12: Average Fare by Rating (Line chart)Chart 13: Revenue by Vehicle Type (Horizontal bar)Chart 14: Traffic Level Impact (Bar chart)Chart 15: Daily Trips by App Platform (Line chart)Chart 16: Surge by Platform (Table)ขั้นที่ 4: ตรวจสอบขั้นที่ 5: ส่งออกเพื่อนำกลับมาใช้
TH