@clickzetta/cz-cli-darwin-x64 0.3.17 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cz-cli +0 -0
- package/bin/skills/clickzetta-batch-sync-pipeline/SKILL.md +386 -0
- package/bin/skills/clickzetta-cdc-sync-pipeline/SKILL.md +548 -0
- package/bin/skills/clickzetta-data-ingest-pipeline/SKILL.md +220 -0
- package/bin/skills/clickzetta-data-ingest-pipeline/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-dynamic-table/SKILL.md +112 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/dimension-table-join-guide.md +257 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/medallion-and-stream-patterns.md +124 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/non-partitioned-merge-into-warning.md +96 -0
- package/bin/skills/clickzetta-dynamic-table/best-practices/performance-optimization.md +109 -0
- package/bin/skills/clickzetta-file-import-pipeline/SKILL.md +156 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/SKILL.md +751 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/eval_cases.jsonl +5 -0
- package/bin/skills/clickzetta-kafka-ingest-pipeline/references/kafka-pipe-syntax.md +324 -0
- package/bin/skills/clickzetta-oss-ingest-pipeline/SKILL.md +537 -0
- package/bin/skills/clickzetta-query-optimizer/SKILL.md +156 -0
- package/bin/skills/clickzetta-query-optimizer/references/explain.md +56 -0
- package/bin/skills/clickzetta-query-optimizer/references/hints-and-sortkey.md +78 -0
- package/bin/skills/clickzetta-query-optimizer/references/optimize.md +65 -0
- package/bin/skills/clickzetta-query-optimizer/references/result-cache.md +49 -0
- package/bin/skills/clickzetta-query-optimizer/references/show-jobs.md +42 -0
- package/bin/skills/clickzetta-realtime-sync-pipeline/SKILL.md +276 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/SKILL.md +379 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/evals/evals.json +166 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/dynamic-table.md +185 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/materialized-view.md +129 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/pipe.md +222 -0
- package/bin/skills/clickzetta-sql-pipeline-manager/references/table-stream.md +125 -0
- package/bin/skills/clickzetta-table-stream-pipeline/SKILL.md +206 -0
- package/bin/skills/clickzetta-vcluster-manager/SKILL.md +212 -0
- package/bin/skills/clickzetta-vcluster-manager/references/vc-cache.md +54 -0
- package/bin/skills/clickzetta-vcluster-manager/references/vcluster-ddl.md +150 -0
- package/bin/skills/clickzetta-volume-manager/SKILL.md +292 -0
- package/bin/skills/clickzetta-volume-manager/references/volume-ddl.md +199 -0
- package/package.json +1 -1
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/SKILL.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/dt-declaration-strategy.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/incremental-config-reference.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/refresh-history-guide.md +0 -0
- /package/bin/skills/{dt-creator → clickzetta-dynamic-table/dt-creator}/references/sql-limitations.md +0 -0
- /package/bin/skills/{dynamic-table-alter → clickzetta-dynamic-table/dynamic-table-alter}/SKILL.md +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Medallion 架构与 Table Stream 组合模式
|
|
2
|
+
|
|
3
|
+
## Medallion 三层管道
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Bronze(原始数据)
|
|
7
|
+
↓ Dynamic Table(清洗,INCREMENTAL)
|
|
8
|
+
Silver(清洗数据)
|
|
9
|
+
↓ Dynamic Table(聚合,FULL)
|
|
10
|
+
Gold(指标数据)
|
|
11
|
+
↓ BI 工具直接查询
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Bronze → Silver(增量清洗)
|
|
15
|
+
|
|
16
|
+
```sql
|
|
17
|
+
-- 前提:源表开启变更跟踪
|
|
18
|
+
ALTER TABLE bronze.raw_orders SET PROPERTIES ('change_tracking' = 'true');
|
|
19
|
+
|
|
20
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS silver.orders_cleaned
|
|
21
|
+
REFRESH INTERVAL 15 MINUTE vcluster default
|
|
22
|
+
AS
|
|
23
|
+
SELECT
|
|
24
|
+
order_id,
|
|
25
|
+
customer_id,
|
|
26
|
+
CAST(amount AS DECIMAL(18,2)) AS amount,
|
|
27
|
+
CAST(created_at AS TIMESTAMP) AS created_at,
|
|
28
|
+
COALESCE(region, 'unknown') AS region
|
|
29
|
+
FROM bronze.raw_orders
|
|
30
|
+
WHERE order_id IS NOT NULL AND amount > 0;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Silver → Gold(聚合指标,通常 FULL)
|
|
34
|
+
|
|
35
|
+
```sql
|
|
36
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS gold.orders_daily_summary
|
|
37
|
+
REFRESH INTERVAL 60 MINUTE vcluster default
|
|
38
|
+
AS
|
|
39
|
+
SELECT
|
|
40
|
+
DATE(created_at) AS stat_date,
|
|
41
|
+
region,
|
|
42
|
+
COUNT(*) AS order_count,
|
|
43
|
+
SUM(amount) AS total_revenue,
|
|
44
|
+
COUNT(DISTINCT customer_id) AS unique_customers
|
|
45
|
+
FROM silver.orders_cleaned
|
|
46
|
+
GROUP BY 1, 2;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 与 Table Stream 组合(事件驱动)
|
|
52
|
+
|
|
53
|
+
Table Stream 捕获源表变更,Dynamic Table 消费 Stream 做增量处理。
|
|
54
|
+
|
|
55
|
+
### 基本模式
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- 1. 源表开启变更跟踪
|
|
59
|
+
ALTER TABLE bronze.raw_orders SET PROPERTIES ('change_tracking' = 'true');
|
|
60
|
+
|
|
61
|
+
-- 2. 创建 Table Stream
|
|
62
|
+
CREATE TABLE STREAM bronze.orders_stream
|
|
63
|
+
ON TABLE bronze.raw_orders
|
|
64
|
+
WITH PROPERTIES ('TABLE_STREAM_MODE' = 'STANDARD');
|
|
65
|
+
|
|
66
|
+
-- 3. Dynamic Table 消费 Stream
|
|
67
|
+
-- 注意:Stream 作为 DT 源时,每次刷新会消费 offset
|
|
68
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS silver.orders_incremental
|
|
69
|
+
REFRESH INTERVAL 5 MINUTE vcluster default
|
|
70
|
+
AS
|
|
71
|
+
SELECT order_id, customer_id, amount, status
|
|
72
|
+
FROM bronze.orders_stream
|
|
73
|
+
WHERE __change_type IN ('INSERT', 'UPDATE_AFTER');
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### MERGE INTO + Table Stream(替代非分区 DT 的去重场景)
|
|
77
|
+
|
|
78
|
+
当需要按主键去重且源表持续写入时,推荐用 MERGE INTO 替代 Dynamic Table:
|
|
79
|
+
|
|
80
|
+
```sql
|
|
81
|
+
-- 1. 创建 Table Stream
|
|
82
|
+
CREATE TABLE STREAM source_stream ON TABLE source_table
|
|
83
|
+
WITH PROPERTIES ('TABLE_STREAM_MODE' = 'STANDARD', 'SHOW_INITIAL_ROWS' = 'TRUE');
|
|
84
|
+
|
|
85
|
+
-- 2. 创建目标表
|
|
86
|
+
CREATE TABLE target_table (
|
|
87
|
+
id BIGINT,
|
|
88
|
+
col1 STRING,
|
|
89
|
+
col2 INT,
|
|
90
|
+
event_time TIMESTAMP
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
-- 3. 定时调度 MERGE INTO 消费 Stream
|
|
94
|
+
MERGE INTO target_table t
|
|
95
|
+
USING (
|
|
96
|
+
SELECT id, col1, col2, event_time,
|
|
97
|
+
CASE WHEN `value` IS NULL OR `value` = '' THEN 'DELETE' ELSE 'UPSERT' END AS op
|
|
98
|
+
FROM source_stream
|
|
99
|
+
) s ON t.id = s.id
|
|
100
|
+
WHEN MATCHED AND s.op = 'UPSERT' THEN UPDATE SET
|
|
101
|
+
t.col1 = s.col1, t.col2 = s.col2, t.event_time = s.event_time
|
|
102
|
+
WHEN NOT MATCHED AND s.op = 'UPSERT' THEN INSERT
|
|
103
|
+
(id, col1, col2, event_time) VALUES (s.id, s.col1, s.col2, s.event_time);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 实时报表物化
|
|
109
|
+
|
|
110
|
+
```sql
|
|
111
|
+
-- 每小时刷新销售汇总,供 BI 工具直接查询
|
|
112
|
+
CREATE DYNAMIC TABLE IF NOT EXISTS rpt.sales_hourly
|
|
113
|
+
REFRESH INTERVAL 60 MINUTE vcluster default
|
|
114
|
+
AS
|
|
115
|
+
SELECT
|
|
116
|
+
DATE_TRUNC('hour', order_time) AS hour_bucket,
|
|
117
|
+
product_category,
|
|
118
|
+
SUM(amount) AS revenue,
|
|
119
|
+
COUNT(*) AS order_cnt,
|
|
120
|
+
AVG(amount) AS avg_order_value
|
|
121
|
+
FROM silver.orders_cleaned
|
|
122
|
+
WHERE order_time >= DATEADD(day, -30, CURRENT_DATE)
|
|
123
|
+
GROUP BY 1, 2;
|
|
124
|
+
```
|
package/bin/skills/clickzetta-dynamic-table/best-practices/non-partitioned-merge-into-warning.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 非分区表 + 持续写入:DT 风险告警与 MERGE INTO 替代建议
|
|
2
|
+
|
|
3
|
+
## 触发条件
|
|
4
|
+
|
|
5
|
+
当用户要创建的 DT 同时满足以下条件时,**必须向用户发出告警**:
|
|
6
|
+
|
|
7
|
+
1. DT 本身是非分区表(没有 `PARTITIONED BY`,也没有 `SESSION_CONFIGS()` 引用)
|
|
8
|
+
2. 源表也是非分区表,且数据会持续写入(如 Kafka 消费落地表、CDC 明细表)
|
|
9
|
+
3. SQL 中包含按主键去重的窗口函数模式:`ROW_NUMBER() OVER (PARTITION BY key ORDER BY ts DESC) WHERE rn = 1`
|
|
10
|
+
|
|
11
|
+
## 告警内容
|
|
12
|
+
|
|
13
|
+
向用户说明以下三个风险:
|
|
14
|
+
|
|
15
|
+
### 风险 1:存储无限膨胀
|
|
16
|
+
|
|
17
|
+
非分区的 DT 和非分区的源表都没有自动的数据生命周期管理机制(`data_lifecycle` 仅对分区表生效)。随着数据持续写入:
|
|
18
|
+
- 源表数据量无限增长
|
|
19
|
+
- DT 的状态表全局维护,随数据量线性膨胀
|
|
20
|
+
- 目标表数据量同步增长
|
|
21
|
+
- 三者叠加,存储成本持续上升且不可控
|
|
22
|
+
|
|
23
|
+
### 风险 2:源表归档引发性能灾难
|
|
24
|
+
|
|
25
|
+
当存储膨胀到一定程度,运维人员通常会对源表进行归档——将历史数据迁移到冷存储或归档表,然后从源表中删除以释放空间。此时:
|
|
26
|
+
|
|
27
|
+
- DT 会捕获源表的删除事件,并将其反映到增量计算结果中
|
|
28
|
+
- `ROW_NUMBER() OVER (PARTITION BY key ORDER BY ts DESC) WHERE rn = 1` 的删除处理代价极高:
|
|
29
|
+
- 窗口函数无法增量处理删除——需要回读该 key 下的所有历史数据重新排序
|
|
30
|
+
- 非分区表没有分区边界来限制回读范围,可能需要扫描整张表
|
|
31
|
+
- 大规模归档会产生海量删除变更,每个 key 都需要独立回算
|
|
32
|
+
- 一次源表归档可能导致 DT REFRESH 耗时从秒级飙升到小时级甚至失败
|
|
33
|
+
|
|
34
|
+
### 风险 3:无法过滤归档产生的删除事件
|
|
35
|
+
|
|
36
|
+
DT 的增量引擎自动捕获源表的所有变更(INSERT / UPDATE / DELETE),用户无法干预这个过程。SQL 中的 `WHERE op <> 'DELETE'` 过滤的是业务层面的删除标记,而不是源表物理删除产生的删除变更。用户没有任何手段告诉 DT "这些删除是归档操作,请忽略"。
|
|
37
|
+
|
|
38
|
+
## 推荐替代方案
|
|
39
|
+
|
|
40
|
+
建议用户使用 MERGE INTO + Table Stream 替代:
|
|
41
|
+
|
|
42
|
+
```sql
|
|
43
|
+
-- Step 1: 源表开启变更跟踪
|
|
44
|
+
ALTER TABLE source_table SET PROPERTIES ('change_tracking' = 'true');
|
|
45
|
+
|
|
46
|
+
-- Step 2: 创建 Table Stream
|
|
47
|
+
CREATE TABLE STREAM source_stream ON TABLE source_table
|
|
48
|
+
WITH (TABLE_STREAM_MODE = 'STANDARD', SHOW_INITIAL_ROWS = TRUE);
|
|
49
|
+
|
|
50
|
+
-- Step 3: 创建目标表
|
|
51
|
+
CREATE TABLE target_table (
|
|
52
|
+
id BIGINT,
|
|
53
|
+
col1 STRING,
|
|
54
|
+
col2 INT,
|
|
55
|
+
event_time TIMESTAMP
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- Step 4: 定时调度 MERGE INTO 消费 Stream
|
|
59
|
+
MERGE INTO target_table t
|
|
60
|
+
USING (
|
|
61
|
+
SELECT id, col1, col2, event_time,
|
|
62
|
+
CASE WHEN `value` IS NULL OR `value` = '' THEN 'DELETE' ELSE 'UPSERT' END AS op
|
|
63
|
+
FROM source_stream
|
|
64
|
+
) s ON t.id = s.id
|
|
65
|
+
WHEN MATCHED AND s.op = 'UPSERT' THEN UPDATE SET
|
|
66
|
+
t.col1 = s.col1, t.col2 = s.col2, t.event_time = s.event_time
|
|
67
|
+
WHEN NOT MATCHED AND s.op = 'UPSERT' THEN INSERT
|
|
68
|
+
(id, col1, col2, event_time) VALUES (s.id, s.col1, s.col2, s.event_time);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
MERGE INTO + Table Stream 的优势:
|
|
72
|
+
- **每次计算独立**:只消费 Stream 中的增量数据,不依赖源表全量状态
|
|
73
|
+
- **归档免疫**:源表归档时,可在 USING 子查询中通过 WHERE 条件过滤归档产生的删除事件
|
|
74
|
+
- **目标表独立管理**:目标表的生命周期与源表解耦,可独立制定归档策略
|
|
75
|
+
- **offset 自动推进**:MERGE INTO 消费 Stream 后 offset 自动推进,下次只处理新变更
|
|
76
|
+
|
|
77
|
+
## 告警话术模板
|
|
78
|
+
|
|
79
|
+
当检测到用户的 DT 满足触发条件时,使用以下话术:
|
|
80
|
+
|
|
81
|
+
> ⚠️ **风险提示**:您正在创建一个非分区的 Dynamic Table,且源表也是非分区的持续写入表。这种组合存在以下长期运维风险:
|
|
82
|
+
>
|
|
83
|
+
> 1. **存储无限膨胀**:源表、DT 目标表、DT 状态表三者都会持续增长,且无法通过 `data_lifecycle` 自动清理
|
|
84
|
+
> 2. **源表归档会引发性能灾难**:当您需要对源表进行归档(迁移历史数据后删除)时,DT 会捕获这些删除事件。由于 SQL 中包含 `ROW_NUMBER() ... WHERE rn = 1` 的去重逻辑,每个被删除的 key 都需要回读历史数据重新排序,非分区表没有边界限制,可能导致 REFRESH 性能严重回退
|
|
85
|
+
> 3. **无法过滤归档删除**:DT 增量引擎自动捕获源表所有变更,您无法告诉 DT 忽略归档操作产生的删除
|
|
86
|
+
>
|
|
87
|
+
> **建议**:对于这类"非分区 CDC 明细表合并为结果表"的场景,推荐使用 MERGE INTO + Table Stream 方案。每次只消费增量数据,源表归档时可通过 WHERE 条件过滤删除事件,不会影响下游。
|
|
88
|
+
|
|
89
|
+
## 判断逻辑
|
|
90
|
+
|
|
91
|
+
在帮助用户创建 DT 时,按以下顺序检查:
|
|
92
|
+
|
|
93
|
+
1. DT 是否有 `PARTITIONED BY` 或 `SESSION_CONFIGS()` → 如果有,不触发告警
|
|
94
|
+
2. 源表是否是持续写入的非分区表(如 Kafka 消费表、CDC 明细表)→ 如果不是,不触发告警
|
|
95
|
+
3. SQL 是否包含 `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ... DESC) WHERE rn = 1` 模式 → 如果包含,风险最高,必须告警
|
|
96
|
+
4. 即使没有 ROW_NUMBER,只要满足条件 1+2,也应提醒用户注意存储膨胀风险
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Dynamic Table 性能优化指南
|
|
2
|
+
|
|
3
|
+
本文档从 SQL 写法、数据特征、管道设计三个维度,帮助用户写出增量刷新性能更好的 DT。
|
|
4
|
+
|
|
5
|
+
## 核心原则:增量刷新的代价模型
|
|
6
|
+
|
|
7
|
+
增量刷新的性能取决于三个因素:
|
|
8
|
+
1. **变更量占比**:每次刷新时,源表中有多少数据发生了变化。变更量越小,增量越划算
|
|
9
|
+
2. **算子类型**:不同 SQL 算子的增量代价差异很大
|
|
10
|
+
3. **数据局部性**:变更数据在 JOIN key / GROUP BY key / PARTITION BY key 上的分布是否集中
|
|
11
|
+
|
|
12
|
+
当变更量超过总数据量的较大比例时,增量刷新可能反而比全量刷新更慢,因为增量需要额外的变更数据计算、去重合并、状态表读写等开销。
|
|
13
|
+
|
|
14
|
+
## SQL 写法优化
|
|
15
|
+
|
|
16
|
+
### 1. 优先使用 INNER JOIN 而非 OUTER JOIN
|
|
17
|
+
|
|
18
|
+
INNER JOIN 的增量计算比 OUTER JOIN 更高效:
|
|
19
|
+
- INNER JOIN:只需要计算 A 的变更数据 JOIN B 的全量数据 + A 的全量数据 JOIN B 的变更数据
|
|
20
|
+
- LEFT/RIGHT/FULL OUTER JOIN:还需要额外处理 NULL 填充、反向撤回等逻辑
|
|
21
|
+
|
|
22
|
+
如果业务上可以保证参照完整性(即 JOIN key 一定能匹配),优先用 INNER JOIN。
|
|
23
|
+
|
|
24
|
+
```sql
|
|
25
|
+
-- ❌ 不必要的 LEFT JOIN(如果 product 一定存在)
|
|
26
|
+
SELECT o.*, p.name FROM orders o LEFT JOIN products p ON o.pid = p.id;
|
|
27
|
+
|
|
28
|
+
-- ✅ 改用 INNER JOIN
|
|
29
|
+
SELECT o.*, p.name FROM orders o INNER JOIN products p ON o.pid = p.id;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. 减少不必要的 DISTINCT
|
|
33
|
+
|
|
34
|
+
每次增量刷新时,DISTINCT 需要对受影响的 key 做重算。如果上游数据已经去重,或者可以通过其他方式保证唯一性,去掉 DISTINCT。
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
-- ❌ 冗余的 DISTINCT
|
|
38
|
+
SELECT DISTINCT user_id, user_name FROM user_events;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 3. 窗口函数必须有 PARTITION BY
|
|
42
|
+
|
|
43
|
+
没有 PARTITION BY 的窗口函数会导致每次增量刷新都全量重算整个窗口。加上 PARTITION BY 后,只需要重算受影响的分区。
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
-- ❌ 全局窗口,每次增量都全量重算
|
|
47
|
+
SELECT *, ROW_NUMBER() OVER (ORDER BY created_at DESC) AS rn FROM events;
|
|
48
|
+
|
|
49
|
+
-- ✅ 加上 PARTITION BY,只重算有变更的分区
|
|
50
|
+
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS rn FROM events;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 4. 聚合 key 尽量使用简单列引用
|
|
54
|
+
|
|
55
|
+
复合表达式作为 GROUP BY key 会降低增量效率,因为引擎需要对表达式求值后才能判断哪些 key 受影响。
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- ❌ 复合表达式作为 GROUP BY key
|
|
59
|
+
SELECT DATE_TRUNC('hour', ts) AS hour, SUM(amount)
|
|
60
|
+
FROM transactions
|
|
61
|
+
GROUP BY DATE_TRUNC('hour', ts);
|
|
62
|
+
|
|
63
|
+
-- ✅ 如果可能,在上游预计算好 key 列
|
|
64
|
+
-- 或者拆分为两个 DT(见下文"管道拆分")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 5. 尽可能使用分区条件限制数据范围
|
|
68
|
+
|
|
69
|
+
在 DT 的 SQL 中对源表添加分区过滤条件,可以显著减少每次增量刷新需要扫描的数据量。
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
-- ❌ 不加分区条件,每次扫描全表
|
|
73
|
+
SELECT o.*, p.name
|
|
74
|
+
FROM orders o JOIN products p ON o.pid = p.id;
|
|
75
|
+
|
|
76
|
+
-- ✅ 通过分区条件限制数据范围
|
|
77
|
+
SELECT o.*, p.name
|
|
78
|
+
FROM orders o JOIN products p ON o.pid = p.id
|
|
79
|
+
WHERE o.ds = SESSION_CONFIGS()['dt.args.ds'];
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 管道拆分:复杂 DT 拆成多级
|
|
83
|
+
|
|
84
|
+
当一个 DT 的 SQL 包含多个 JOIN + 聚合 + 窗口函数时,考虑拆分为多个 DT,每个 DT 只做一件事。
|
|
85
|
+
|
|
86
|
+
好处:
|
|
87
|
+
- 每个 DT 的增量计算更简单、更快
|
|
88
|
+
- 中间 DT 可以被多个下游 DT 复用
|
|
89
|
+
- 出问题时更容易定位是哪一层的问题
|
|
90
|
+
- 不同层可以使用不同的优化策略
|
|
91
|
+
|
|
92
|
+
## 数据特征与增量效率
|
|
93
|
+
|
|
94
|
+
### 变更量占比
|
|
95
|
+
|
|
96
|
+
增量刷新在变更量占总数据量比例较小时效果最好。经验值:
|
|
97
|
+
- < 5%:增量刷新通常显著优于全量
|
|
98
|
+
- 5% ~ 20%:取决于具体算子和数据分布
|
|
99
|
+
- \> 20%:可能需要评估是否全量刷新更合适
|
|
100
|
+
|
|
101
|
+
### Append-Only 源表
|
|
102
|
+
|
|
103
|
+
如果源表只有 INSERT 没有 UPDATE/DELETE 可以显著优化:
|
|
104
|
+
- 增量引擎知道变更数据只有新增(无撤回),可以跳过去重合并等操作
|
|
105
|
+
- 聚合可以直接累加,不需要维护完整的中间状态
|
|
106
|
+
|
|
107
|
+
### 变更数据的分布
|
|
108
|
+
|
|
109
|
+
如果变更数据集中在少数 key 上(如最近时间段的数据),增量效率高。如果变更分散在大量 key 上,聚合和窗口函数需要重算大量分区,效率下降。
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clickzetta-file-import-pipeline
|
|
3
|
+
description: |
|
|
4
|
+
从 URL、本地文件或 Volume 路径将数据导入到 ClickZetta 表中,覆盖文件下载、格式推断、
|
|
5
|
+
表创建、COPY INTO 导入、结果验证的完整流程。当用户说"导入数据"、"从 URL 加载"、
|
|
6
|
+
"上传 CSV 到表"、"文件导入"、"COPY INTO"时触发。包含 ClickZetta USER VOLUME 机制、
|
|
7
|
+
COPY INTO 语法、格式推断规则、写入模式语义等平台特有知识。
|
|
8
|
+
Keywords: file import, URL, CSV, JSON, Parquet, COPY INTO, Volume
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# URL/文件数据导入工作流
|
|
12
|
+
|
|
13
|
+
## 指令
|
|
14
|
+
|
|
15
|
+
### 步骤 1:获取源文件并上传到 Volume
|
|
16
|
+
根据数据来源选择对应方式:
|
|
17
|
+
- **HTTP/HTTPS URL**:需要先用外部工具下载到本地,然后用 `PUT` 命令上传到 User Volume
|
|
18
|
+
- **本地文件**:执行 SQL `PUT '/local/path/file.csv' TO USER VOLUME` 上传
|
|
19
|
+
- **Volume 路径**:文件已在 Volume 上,跳过此步骤
|
|
20
|
+
- **外部 Volume(OSS/S3/COS)**:文件已在外部 Volume,直接使用
|
|
21
|
+
- 记录上传后的 Volume 名称和文件名,后续步骤需要
|
|
22
|
+
|
|
23
|
+
> ⚠️ **注意**:文件上传操作参考 `clickzetta-volume-manager` skill。
|
|
24
|
+
|
|
25
|
+
### 步骤 2:推断文件格式
|
|
26
|
+
根据文件扩展名推断格式(ClickZetta COPY INTO 支持的格式):
|
|
27
|
+
- `.csv`, `.tsv`, `.txt` → CSV 格式
|
|
28
|
+
- `.json`, `.jsonl`, `.ndjson` → JSON 格式
|
|
29
|
+
- `.parquet`, `.pq` → PARQUET 格式
|
|
30
|
+
- `.orc` → ORC 格式
|
|
31
|
+
- `.bson` → BSON 格式
|
|
32
|
+
如果扩展名不明确,执行 `SELECT FROM VOLUME ... USING format` 预览文件内容来确认格式和 schema。
|
|
33
|
+
|
|
34
|
+
### 步骤 3:确认或创建目标表
|
|
35
|
+
根据写入模式处理目标表:
|
|
36
|
+
- **create 模式**:表必须不存在。执行 `SELECT FROM VOLUME ... LIMIT 5` 推断 schema,然后执行 `CREATE TABLE` 创建表
|
|
37
|
+
- **append 模式**:表必须已存在。用 `DESC TABLE <table_name>` 确认表存在并检查列兼容性
|
|
38
|
+
- **overwrite 模式**:表存在则先清空。执行 `TRUNCATE TABLE table_name`,再执行 COPY INTO(⚠️ 不支持 `COPY OVERWRITE INTO` 语法)
|
|
39
|
+
|
|
40
|
+
### 步骤 4:执行 COPY INTO 导入数据
|
|
41
|
+
执行 COPY INTO 语句。核心语法:
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
COPY INTO target_table
|
|
45
|
+
FROM VOLUME volume_name
|
|
46
|
+
USING format_type
|
|
47
|
+
OPTIONS('option_name' = 'value')
|
|
48
|
+
FILES('filename');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
对于 USER VOLUME(通过 PUT 命令上传的文件):
|
|
52
|
+
```sql
|
|
53
|
+
COPY INTO target_table
|
|
54
|
+
FROM USER VOLUME
|
|
55
|
+
USING CSV
|
|
56
|
+
OPTIONS('header' = 'true')
|
|
57
|
+
FILES('uploaded_filename');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
CSV 格式可附加 OPTIONS:
|
|
61
|
+
```sql
|
|
62
|
+
COPY INTO target_table
|
|
63
|
+
FROM VOLUME vol
|
|
64
|
+
USING CSV
|
|
65
|
+
OPTIONS('header' = 'true', 'sep' = ',', 'quote' = '"', 'nullValue' = '')
|
|
66
|
+
FILES('data.csv');
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
⚠️ **语法顺序要求**:`OPTIONS` 必须在 `FILES` 之前,否则报错 `Syntax error - missing EQ at '('`
|
|
70
|
+
|
|
71
|
+
overwrite 模式(⚠️ 不支持 `COPY OVERWRITE INTO`):
|
|
72
|
+
```sql
|
|
73
|
+
-- 正确方式:先 TRUNCATE 再 COPY
|
|
74
|
+
TRUNCATE TABLE target_table;
|
|
75
|
+
COPY INTO target_table FROM VOLUME vol USING CSV FILES('data.csv');
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 步骤 5:验证导入结果
|
|
79
|
+
执行验证查询:
|
|
80
|
+
```sql
|
|
81
|
+
SELECT COUNT(*) as row_count FROM target_table;
|
|
82
|
+
SELECT * FROM target_table LIMIT 5;
|
|
83
|
+
```
|
|
84
|
+
确认行数符合预期,数据内容正确。
|
|
85
|
+
|
|
86
|
+
## 示例
|
|
87
|
+
|
|
88
|
+
### 示例 1:从 URL 导入 CSV 到新表
|
|
89
|
+
```sql
|
|
90
|
+
-- 1. 下载 URL 文件到本地,然后上传到 User Volume
|
|
91
|
+
PUT '/tmp/data.csv' TO USER VOLUME;
|
|
92
|
+
|
|
93
|
+
-- 2. 预览文件内容推断 schema
|
|
94
|
+
SELECT * FROM USER VOLUME USING CSV OPTIONS('header' = 'true') FILES('data.csv') LIMIT 5;
|
|
95
|
+
-- 推断出列:id INT, name STRING, value DOUBLE
|
|
96
|
+
|
|
97
|
+
-- 3. 创建目标表
|
|
98
|
+
CREATE TABLE imported_data (id INT, name STRING, value DOUBLE);
|
|
99
|
+
|
|
100
|
+
-- 4. 执行 COPY INTO 导入(注意:OPTIONS 必须在 FILES 之前)
|
|
101
|
+
COPY INTO imported_data FROM USER VOLUME USING CSV OPTIONS('header' = 'true') FILES('data.csv');
|
|
102
|
+
|
|
103
|
+
-- 5. 验证导入结果
|
|
104
|
+
SELECT COUNT(*) FROM imported_data;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 示例 2:追加 Parquet 数据到已有表
|
|
108
|
+
```sql
|
|
109
|
+
-- 1. 上传本地文件到 User Volume
|
|
110
|
+
PUT '/local/new_batch.parquet' TO USER VOLUME;
|
|
111
|
+
|
|
112
|
+
-- 2. 确认目标表存在
|
|
113
|
+
DESC TABLE existing_table;
|
|
114
|
+
|
|
115
|
+
-- 3. 执行 COPY INTO 导入(Parquet 格式通常不需要 OPTIONS)
|
|
116
|
+
COPY INTO existing_table FROM USER VOLUME USING PARQUET FILES('new_batch.parquet');
|
|
117
|
+
|
|
118
|
+
-- 4. 验证导入结果
|
|
119
|
+
SELECT COUNT(*) FROM existing_table;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 示例 3:从外部 Volume(OSS)导入
|
|
123
|
+
```sql
|
|
124
|
+
-- 1. 查看 Volume 中的文件列表
|
|
125
|
+
SHOW VOLUME DIRECTORY my_oss_volume;
|
|
126
|
+
|
|
127
|
+
-- 2. 预览文件内容
|
|
128
|
+
SELECT * FROM VOLUME my_oss_volume USING CSV OPTIONS('header' = 'true') FILES('data.csv') LIMIT 5;
|
|
129
|
+
|
|
130
|
+
-- 3. 创建目标表并导入(注意:OPTIONS 必须在 FILES 之前)
|
|
131
|
+
CREATE TABLE imported_data (col1 INT, col2 STRING);
|
|
132
|
+
COPY INTO imported_data FROM VOLUME my_oss_volume USING CSV OPTIONS('header' = 'true') FILES('data.csv');
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 故障排除
|
|
136
|
+
|
|
137
|
+
| 错误 | 原因 | 解决方案 |
|
|
138
|
+
|------|------|----------|
|
|
139
|
+
| COPY INTO 报 "table not found" | create 模式下表未创建,或 append 模式下表名拼写错误 | 先用 `SHOW TABLES` 确认表是否存在 |
|
|
140
|
+
| COPY INTO 报 "file not found" | FILES 中的文件名与 Volume 上的实际文件名不匹配 | 执行 `SHOW VOLUME DIRECTORY vol_name` 或 `SHOW USER VOLUME DIRECTORY` 确认文件名,注意大小写敏感 |
|
|
141
|
+
| COPY INTO 报语法错误 "missing EQ at '('" | OPTIONS 放在了 FILES 之后 | 调整顺序,确保 `OPTIONS` 在 `FILES` 之前:`USING CSV OPTIONS(...) FILES(...)` |
|
|
142
|
+
| CSV 导入列数不匹配 | CSV 文件有 header 行但未指定 `OPTIONS('header'='true')`,导致 header 被当作数据行 | 添加 `OPTIONS('header' = 'true')`,或检查 CSV 分隔符是否正确(sep 参数) |
|
|
143
|
+
| COPY INTO 报 "schema mismatch" | 文件中的数据类型与目标表列定义不兼容 | 执行 `SELECT FROM VOLUME ... USING format LIMIT 5` 预览实际数据,调整表定义或使用列映射 |
|
|
144
|
+
| overwrite 模式数据未清空 | 使用了 `COPY OVERWRITE INTO` 语法(不支持) | overwrite 模式应先用 `TRUNCATE TABLE` 清空表,再执行 `COPY INTO` |
|
|
145
|
+
| SELECT FROM VOLUME 报错 | 格式不匹配或多格式文件混合 | 确认 USING 后的格式与实际文件格式一致;使用 `FILES()` 指定文件或 `SUBDIRECTORY` 指定子目录 |
|
|
146
|
+
| PUT 命令失败 | 本地文件路径不存在 | 确认本地文件路径正确,文件存在 |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 依赖的 Skills
|
|
151
|
+
|
|
152
|
+
| 操作 | 需要加载的 Skill |
|
|
153
|
+
|------|-----------------|
|
|
154
|
+
| 文件上传/下载/删除 | `clickzetta-volume-manager` |
|
|
155
|
+
| 查询 Volume 文件内容 | `clickzetta-volume-manager` |
|
|
156
|
+
| COPY INTO 导入 | 本 Skill |
|