Snowflake MigrationClickHouse Workshops
规划练习表

练习表 3:Schema 转换

把 TRIPS_RAW 和 FACT_TRIPS 的每一列映射到对应的 ClickHouse 类型,并翻译七个 Snowflake 表达式,每个答案都有即时反馈。

预计耗时: 20–25 分钟 参考资料: Snowflake 与 ClickHouse 对照,第 2 节(SQL 方言差异)

概念

类型映射和函数翻译是迁移中最机械的部分,但如果做得草率, 也是最容易出错的部分。Snowflake 和 ClickHouse 的类型 系统不同,语义也不同,用错类型会导致无声的精度 丢失、存储浪费或查询逻辑错误。

关键原则:

  1. 对精度要显式。 Snowflake 的 TIMESTAMP_NTZ(9) 具有纳秒 精度。ClickHouse 的 DateTime 只有秒级精度,不要把它用于 版本列。用 DateTime64(3, 'UTC') 表示毫秒精度(符合大多数 现实需求),或用 DateTime64(9, 'UTC') 表示纳秒。这关乎 正确性: 如果 ReplacingMergeTree 的版本列只有秒级精度,同一秒内 到达的两次更新就是不确定的,ClickHouse 无法 判断哪一个更新。

  2. 使用最小的正确整数类型。 Snowflake 的 INTEGER 就是 NUMBER(38, 0), 以 128 位值存储的 38 位定点精度。ClickHouse 有定宽 整数:Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32、UInt64。 为 vendor_id(取值 1–3)选择 UInt8,相比 Int64 每行节省 7 字节。在 5000 万 行上,就是 350MB。

  3. VARIANT → String。 ClickHouse 有原生的 JSON 类型(v25.3+ 起 生产可用),但它是为真正动态的 schema 设计的,即建表时字段名 和结构都未知的情况。对本实验中的 trip_metadata 而言, 结构是已知的(driver.rating、app.surge_multiplier 等),更好的 做法是在迁移过程中预先拍平成带类型的列,或者存为 String 并在查询时使用 JSONExtract*。当你确实无法 预测 schema 时才用 JSON 类型:例如摄取任意客户事件负载,其中每个事件 都有不同字段。("预先拍平成带类型的列"指的是在 ETL 过程中把字段抽取到 独立的顶层列中,就像 FACT_TRIPS.driver_rating 从 trip_metadata 产出的方式,而不是把 JSON 块本身包进一个 Tuple。Tuple 列 仍然锁定在一组固定字段上,因此只要某次行程的元数据 不符合那个形状,它就会失效。)

  4. 浮点精度。 Snowflake 的 FLOAT 在 ClickHouse 中映射为 Float64。对于需要 精确十进制运算的金额,使用 Decimal(18, 2),但对 本实验来说,Float64 足以与源端保持一致。(这是默认做法,不是说 每个 FLOAT 列都不论取值范围一律取 Float64:像 driver_rating 这样取值在 1.0–5.0、只保留一位小数的列,完全可以放进 Float32 约 7 位有效数字的范围,那里的选择取决于可空性,而不是 规则 4 为金额所保护的精度。)

  5. LowCardinality(),ClickHouse 独有的优化。 把一个类型包进 LowCardinality(String)(或 LowCardinality(UInt8) 等)会告诉 ClickHouse 对该列使用 字典编码,值以指向字典的整数引用形式存储,而不是重复的 字符串。对不同值少于约 10,000 个的字符串列,这通常能带来 2–5 倍的压缩 提升和更快的 GROUP BY。Snowflake 没有等价物;它自动处理这件事。本实验中的好 候选:pickup_borough(6 个值)、payment_type(6 个值)、vehicle_type、 vendor_name。

练习:TRIPS_RAW 的类型映射

把 NYC_TAXI_DB.RAW.TRIPS_RAW 的每一列映射到对应的 ClickHouse 类型。TRIP_ID 已经 作为示例填好:从 VARCHAR(36) 迁移过来时,String 是惯用做法,它 不需要任何转换,支持所有字符串函数,并且避免了插入时的 UUID 解析开销, 即便 ClickHouse 同样有原生的 UUID 类型。

练习:FACT_TRIPS 的类型映射

FACT_TRIPS 增加了由 dbt 管道添加的计算列/派生列。大多数 列沿用 TRIPS_RAW 的决策;DRIVER_RATING 和 UPDATED_AT 是新增的。

DRIVER_RATING 经常为 NULL(未给出评分)。在 ClickHouse 中,Nullable(Float64) 相比非空列有轻微的性能开销,数据旁会额外存储一个位图, 用于追踪哪些行为 null。这一列的选择在 Nullable(Float32)(显式的 null 语义)和使用哨兵 值(如 -1.0)的裸 Float32(更快,但不那么常规)之间。本实验为了 正确性选用 Nullable(Float32)。

练习:函数翻译

把每个 Snowflake 表达式翻译成对应的 ClickHouse 等价写法。它们直接 取自 01-setup-snowflake/queries/ 中的 Q1–Q7。八个当中有三个,QUALIFY、MERGE INTO 以及 CDC 流读取,答案无法用一行表达式给出,所以它们改为 在表格下方以问题形式作答。

反思问题

上面的表格填完之后,再来做这些。三个来自练习 3 中 需要多行才能回答的翻译;三个来自源练习表的 "Non-Obvious Translation Decisions",即上面 TRIP_METADATA、FARE_AMOUNT 和 PICKUP_LOCATION_ID 类型选择背后的推理。

Loading worksheet...

转录到 migration-plan.md

把你的类型决策以及任何不显而易见的翻译说明抄到 migration-plan.md 的第 5 节,并勾选:

- [ ] Schema translation: completed

本页内容

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

ZH