Compare commits

..

7 Commits

37 changed files with 8514 additions and 27 deletions

BIN
one-skill/.DS_Store vendored Normal file

Binary file not shown.

BIN
one-skill/smart-data-developer/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,433 @@
---
name: smart-data-developer
description: 智能数据开发员工。支持数据开发和简单取数两种模式。数据开发模式按序加载 agents/requirement-analyzer.md → agents/model-design-generator.md → agents/write-sql.md简单取数模式直接加载 agents/write-sql.md 生成查询SQL。默认使用 OV 搜索表结构、历史经验和 SQL 片段legacy-data-structure-fetcher 仅作为 fallback。当用户提到数据需求、指标需求、报表需求、SQL查询、数据开发、统计需求、ETL任务、取数、查数据时触发此技能。
---
# 智能数据开发员工
## 技能定位
作为**协调者**,根据用户需求类型选择对应流程,完成 SQL 生成。
支持两种模式:
| 模式 | 适用场景 | 流程 |
|------|----------|------|
| **数据开发** | 建表、ETL、定期调度、指标报表、需要需求文档 | 三步走:需求分析 → 模型设计 → SQL编写 |
| **简单取数** | 临时查数据、单表/简单JOIN查询、一次性统计 | 直接调用 write-sql |
---
## 本 skill 内部结构
本 skill 是单包结构,不再依赖外部同名 skill。需要子流程时直接读取本目录下的 agent 或 reference 文件:
| 类型 | 路径 | 用途 |
|------|------|------|
| agent | `agents/requirement-analyzer.md` | 数据开发模式步骤1需求分析、OV 优先数据源匹配、生成 REQ 文档 |
| agent | `agents/model-design-generator.md` | 数据开发模式步骤2读取 REQ 文档,生成 MDDS 文档 |
| agent | `agents/write-sql.md` | 数据开发模式步骤3或简单取数生成 SQL |
| reference | `references/ov-search-context.md` | OV 命令参考 |
| reference | `references/sql/reference/{engine}-sql-syntax.md` | Spark/Doris/Hive/Kudu 引擎语法参考 |
| reference | `references/sql/templates/{engine}/` | SQL 模板 |
| legacy | `references/legacy-data-structure-fetcher/` | OV 不可用、召回不足或用户指定时的数据源匹配 fallback |
---
## Conda 环境配置
不同检索能力需要在不同 conda 环境中运行:
| 功能 | 环境 | 激活方式 |
|-----|------|---------|
| OV 检索OpenViking 知识库) | `py13` | `conda run -n py13 ov ...` |
| legacy data-structure-fetcher旧数据源匹配 | `my_opencode` | `conda run -n my_opencode python script.py` |
### OV 核心资源
| 资源路径 | 用途 | 搜索策略 |
|---------|------|---------|
| `viking://resources/field-process-memory` | 字段处理经验、常用数据源判断、业务经验 | **优先检索**:语义 find + 精确 grep |
| `viking://resources/table-metadata` | 表元信息(表结构、字段定义) | 精确 grep + 语义 find |
| `viking://resources/sql_snippets` | 成功 SQL 代码片段 | 语义 find + 精确 grep |
运行任何 OV 命令之前,先读取相关 reference默认先看 `references/ov-search-context.md` 确认命令用法;涉及 SQL 引擎语法时,再看 `references/sql/reference/{engine}-sql-syntax.md`
### OV 搜索强制执行时机
| 时机 | 搜索内容 | 强制性 |
|-----|---------|--------|
| 步骤1 - 数据源匹配 | 对候选表执行表结构、成功 SQL、常用数据源判断、字段处理经验、业务语义检索 | MUST |
| 步骤1 - 核心字段确认后 | 对核心字段进行专项检索 | MUST |
| 步骤2 - 模型设计前 | 检索分层规范、参考案例 | MUST |
| 步骤3 - SQL 编写前 | 检索语法、成功案例、历史 SQL 片段 | MUST |
### 困惑场景先查 OV
当需要找源表、确认字段、参考历史代码、确认处理口径,或在模型设计/SQL 编写中遇到任何不确定点时,先查 OV 记忆和经验,再继续设计或写 SQL。不要凭经验猜表结构、字段含义、历史口径或 SQL 写法。
| 困惑场景 | 优先检索 |
|---------|---------|
| 不知道用哪张表 | 先查 `viking://resources/field-process-memory`,再查 `viking://resources/table-metadata` |
| 表名或字段含义不确定 | 先查 `viking://resources/field-process-memory`,再查 `viking://resources/table-metadata` |
| 需要字段处理经验、口径经验、常用数据源判断 | `viking://resources/field-process-memory` |
| 需要 SQL 写法、历史成功案例、代码参考 | 先查 `viking://resources/field-process-memory` 判断业务口径,再查 `viking://resources/sql_snippets` |
| SQL 语法或引擎差异不确定 | 先查 `references/sql/reference/`,再查 `viking://resources/sql_snippets` |
### OV 优先 + legacy fallback
默认使用 OV 搜索,不主动调用 legacy data-structure-fetcher。只有以下情况才可以考虑 legacy fallback且**必须先告知用户原因并获得明确同意**,再读取 `references/legacy-data-structure-fetcher/SKILL.md` 并运行其脚本:
1. OV 命令失败或本地不可用;
2. OV 搜索返回的候选表不足以进入用户确认;
3. 用户明确要求使用旧数据字典匹配脚本。
告知模板:
```text
OV 检索结果不足/不可用。是否同意我使用 legacy data-structure-fetcher 脚本补充搜索数据源?
```
---
## 工作流程
```
用户输入需求
[入口] 场景判断 + 用户确认
├── 数据开发 → [步骤1] 需求分析 → 确认 → [步骤2] 模型设计 → 确认 → [步骤3] SQL编写 → 交付
└── 简单取数 → [确认引擎] → 直接调用 write-sql → 交付
```
---
## 入口:场景判断
用户输入需求后,**必须先判断场景类型并让用户确认**。
### 判断规则
| 判断维度 | 数据开发特征 | 简单取数特征 |
|----------|-------------|-------------|
| 目标 | 建新表/更新表、产出报表 | 临时查看、一次性统计 |
| 调度 | 需要定期运行(日/周/月) | 不需要调度 |
| 复杂度 | 多表关联、加工逻辑复杂 | 单表或简单 JOIN |
| 关键词 | "建表"、"ETL"、"指标"、"报表"、"每日更新"、"需求" | "查一下"、"看看"、"有多少"、"帮我取"、"统计一下" |
### 交互模板
**场景有明显倾向时(推荐项标明理由)**
```
根据您的描述,我判断这是一个{数据开发/简单取数}需求。
请确认需求类型:
1. {推荐项}(推荐)← {推荐理由}
2. {另一选项}
回复 1 或 2。
```
**推荐规则**
- 涉及建表、定期调度、多步骤加工 → **推荐数据开发**
- 涉及"查一下"、"看看"、单表简单统计 → **推荐简单取数**
- 模糊场景 → **推荐数据开发**(走三步走不会出错,中途可切换)
**示例1**
```
用户:"帮我统计各部门最近7天的投诉量每天更新到报表里"
助手:
根据您的描述("每天更新到报表"),我判断这是一个数据开发需求。
请确认需求类型:
1. 数据开发(推荐)← 需要定期调度、产出报表
2. 简单取数 ← 只是临时查一下
回复 1 或 2。
```
**示例2**
```
用户:"看一下 db_olap.orders 今天有多少条记录"
助手:
根据您的描述,我判断这是一个简单取数需求。
请确认需求类型:
1. 简单取数(推荐)← 单表查询,无需建表和调度
2. 数据开发 ← 需要建表和 ETL 流程
回复 1 或 2。
```
**示例3模糊场景**
```
用户:"统计一下投诉受理量"
助手:
您的需求可以简单查一下,也可以做成定期更新的报表。
请确认需求类型:
1. 数据开发(推荐)← 如果需要建表、定期调度
2. 简单取数 ← 如果只是临时看一下
回复 1 或 2。
```
**用户确认后**,进入对应流程分支。
---
## 分支A数据开发三步走
### 文件流转链路
三个步骤的产出物统一保存在 `./ai_text/` 目录下,步骤间有严格的依赖关系:
```
步骤1 产出 步骤2 产出 步骤3 产出
./ai_text/REQ-DATA-{ts}-{seq}.md → ./ai_text/MDDS-DATA-{ts}-{seq}.md → ./ai_text/SQL-DATA-{ts}-{seq}.sql
│ │ │
└──── 步骤2 读取此文件 ───────────────┘ │
└──── 步骤3 读取此文件 ──────────────┘
```
| 步骤 | 产出文件 | 命名规则 | 依赖 |
|------|---------|---------|------|
| 1 需求分析 | `REQ-DATA-{YYYYMMDDHHmmss}-{XXX}.md` | 由 requirement-analyzer 生成 | 无 |
| 2 模型设计 | `MDDS-DATA-{YYYYMMDDHHmmss}-{XXX}.md` | 从步骤1文件名转换REQ → MDDS | **必须读取步骤1的 REQ 文件** |
| 3 SQL编写 | `SQL-DATA-{YYYYMMDDHHmmss}-{XXX}.sql` | 从步骤1文件名转换REQ → SQL | **必须读取步骤1的 REQ + 步骤2的 MDDS 文件** |
### 步骤1需求分析
1. 读取并执行 `agents/requirement-analyzer.md`
2. 子技能完成后,展示摘要并等待用户确认:
```
✅ 需求分析已完成!
📄 文件路径:{路径}
请您审核:
- 需求描述是否准确?
- 业务口径是否完整?
- 数据源是否正确?
- 输出字段是否符合预期?
回复"确认"进入步骤2或指出需要修改的内容。
```
3. **记录需求文档路径**到状态中
### 步骤2模型设计
**用户确认需求后**才能进入。本步骤**依赖步骤1的 REQ 文件**
1. 从状态中获取步骤1产出的 REQ 文件路径
2. 读取并执行 `agents/model-design-generator.md`,传入 REQ 文件路径
3. 模型设计 agent 会自动读取 REQ 文件并生成对应的 MDDS 文件到 `./ai_text/`
4. 子技能完成后,展示摘要并等待用户确认:
```
✅ 模型设计已完成!
📄 文件路径:{路径}
请您审核:
- 编排步骤是否合理?
- 目标表属性是否正确?
- 字段设计是否符合预期?
回复"确认"进入步骤3或指出需要修改的内容。
```
3. **记录模型设计文档路径**到状态中
### 步骤3SQL编写
**用户确认模型设计后**才能进入。本步骤由本 skill 主导,调用 write-sql 完成:
#### 3.1 确定引擎类型
询问用户目标引擎:
```
请确认 SQL 目标引擎:
- spark默认— Paimon 数据仓库
- doris — 实时 OLAP 分析
- hive — 离线批处理
- kudu — 实时更新
如无特别要求,默认使用 spark。
```
#### 3.2 读取文件并组装 context
本 skill 负责以下工作(**不是 write-sql 的职责**
1. **读取需求文档**:使用 Read 工具读取步骤1产出的 REQ 文件(`./ai_text/REQ-DATA-xxx.md`
2. **读取模型设计文档**:使用 Read 工具读取步骤2产出的 MDDS 文件(`./ai_text/MDDS-DATA-xxx.md`
3. **组装 context**:将两个文档内容拼接为完整的上下文文本
4. **确定输出路径**从步骤1的 REQ 文件名转换,`REQ-DATA-xxx.md``SQL-DATA-xxx.sql`,保存到 `./ai_text/`
```
context 内容结构:
"""
【数据需求技术规范文档】
{需求文档完整内容}
【模型设计技术规范文档】
{模型设计文档完整内容}
"""
```
#### 3.3 调用 write-sql
```
读取并执行 `agents/write-sql.md`,传入以下参数:
- engine: {用户确认的引擎,默认 spark}
- context: {3.2 组装的完整上下文文本}
- output_path: ./ai_text/SQL-DATA-{从步骤1文件名提取的时间戳和序号}.sql
```
**注意**write-sql 现在是纯函数,不自己读文件,只接收参数生成 SQL。
#### 3.4 验证与交付
write-sql 完成后:
1. 确认 SQL 文件已写入 output_path
2. 简要展示 SQL 脚本概要(步骤数、目标表、源表)
### 数据开发交付
```
✅ 数据开发任务已完成!
交付物清单:
- 需求文档:{路径}
- 模型设计:{路径}
- SQL脚本{路径}
- 目标引擎:{spark/doris/hive/kudu}
```
---
## 分支B简单取数
### B.1 确认引擎
```
请确认查询引擎:
- spark默认
- doris
- hive
- kudu
如无特别要求,默认使用 spark。
```
### B.2 确认补充信息(按需)
如果用户描述中缺少关键信息,**简洁追问**不要用需求分析的13项模板
| 缺失信息 | 追问方式 |
|----------|---------|
| 表名不明 | "请确认要从哪张表查询?" |
| 时间范围不明 | "需要查哪个时间段的数据?" |
| 过滤条件不明 | "有什么筛选条件吗?" |
| 字段不明 | "需要返回哪些字段?还是全部?" |
| 聚合维度不明 | "按什么维度统计?按日/按部门/按地区?" |
**原则**:只问必要的,能推断的不问,能省略的省略。
### B.3 调用 write-sql
```
读取并执行 `agents/write-sql.md`,传入以下参数:
- engine: {用户确认的引擎,默认 spark}
- context: {用户的取数描述 + 补充信息}
- output_path: 无(简单取数默认不写文件,仅在对话中展示)
```
如果用户要求保存到文件:
```
- output_path: ./ai_text/QUERY-{时间戳}.sql
```
### B.4 简单取数交付
```
✅ SQL 已生成!
引擎:{spark/doris/hive/kudu}
```sql
{生成的 SQL}
```
如需调整请告诉我。如需保存到文件,请指定路径。
```
---
## 中途切换
用户在任何时刻可以切换模式:
| 用户说 | 处理方式 |
|--------|---------|
| "这个改成正式的需求" | 简单取数 → 数据开发从步骤1开始 |
| "不用那么复杂,直接帮我查就行" | 数据开发 → 简单取数,用已有信息直接生成 SQL |
| "先简单查一下看看" | 简单取数优先,后续可转数据开发 |
---
## 状态跟踪
```python
state = {
"mode": None, # "dev"(数据开发) | "query"(简单取数) | None待确认
"step": 0, # dev模式0→1→1.5(等待)→2→2.5(等待)→3→4(完成)
# query模式0→B.1→B.2→B.3→4(完成)
"confirmed": [False, False], # dev模式[步骤1确认, 步骤2确认]
"engine": "spark", # 目标引擎
"paths": { # dev模式文件路径
"req": None,
"model": None,
"sql": None
}
}
```
---
## 交互规则
| 场景 | 响应 |
|-----|------|
| 用户首次输入需求 | 场景判断 → 让用户确认模式 |
| 用户确认数据开发 | 进入分支A三步走 |
| 用户确认简单取数 | 进入分支B直接生成SQL |
| 用户确认步骤1/2 | 进入下一步 |
| 用户修改意见 | 调整后重新等待确认 |
| 中途切换模式 | 清理当前状态,进入目标模式 |
| 询问进度 | 告知当前模式、步骤及确认状态 |
| 指定引擎 | 记录到 state.engine |
---
## 输出规范
- **所有产出物统一保存在 `./ai_text/` 目录下**
- **数据开发模式**:需求文档 + 模型设计 + SQL文件三件套文件名保持一致
- 步骤1`./ai_text/REQ-DATA-{ts}-{seq}.md`
- 步骤2`./ai_text/MDDS-DATA-{ts}-{seq}.md`REQ → MDDS
- 步骤3`./ai_text/SQL-DATA-{ts}-{seq}.sql`REQ → SQL
- **简单取数模式**:默认仅在对话中展示 SQL用户要求时写入 `./ai_text/QUERY-{时间戳}.sql`
---
## 完成标志
- **数据开发模式**步骤1/2/3全部完成用户均已确认SQL文件已写入
- **简单取数模式**SQL已生成并展示给用户
---

View File

@@ -0,0 +1,666 @@
---
name: model-design-generator
description: 根据数据需求技术规范文档生成模型设计技术规范文档
---
# 模型设计技能 (Model Design Skill)
## 技能描述
本技能根据**数据需求技术规范文档**,自动生成对应的**模型设计技术规范文档**,包含 SQL 编排步骤、目标表属性信息、目标表字段信息等完整设计内容。
**使用说明**
- 技能加载后**自动**从 `./ai_text/` 目录选取最新的需求文档并开始处理
- 不要询问用户是否使用默认路径,直接自动处理
- 如用户主动传入文件路径或目录路径,则使用用户指定的路径
## 输入输出
### 输入
- **输入类型**MD 文件路径(可选)
- **输入来源**
- 默认:自动从当前目录下的 `ai_text` 文件夹选取最新的 `REQ-DATA-*.md` 文件
- 用户传入文件路径:使用指定文件
- 用户传入目录路径:从指定目录选取最新的 `REQ-DATA-*.md` 文件
- **输入示例**
- 不传参数:自动使用 `./ai_text/REQ-DATA-*.md` 中最新的文件
- 文件路径:`202604/skills_design/v2/data/REQ-DATA-20260409142709-001.md`
- 目录路径:`202604/skills_design/v2/data/` → 自动选取最新的 `REQ-DATA-*.md` 文件
- **重要**:不要询问用户选择输入方式,直接按上述规则自动处理
### 输出
- **输出类型**Markdown + JSON 格式文本(模型设计技术规范文档)
- **输出目录**
- 如果用户指定了输出目录:使用用户指定的目录
- 如果用户未指定输出目录:默认为当前工作目录下的 `ai_text` 文件夹
- **输出内容**
1. 编排目标表生成流程SQL 编排步骤)- Markdown 表格
2. 目标表属性信息 - JSON 格式
3. 目标表字段信息 - JSON 格式
---
## 可配置参数
以下参数按**输出模板的表结构**组织,方便后续统一修改:
---
### 表 1编排目标表生成流程 - 字段规则与配置
输出模板字段:`步骤序号 | sql 编排内容解释 | 输入源表 | 加工字段口径 | 输出表名 | 输出表字段`
| 输出字段 | 填写规则 | 默认值/配置项 | 说明 |
|----------|----------|---------------|------|
| 步骤序号 | 按 `step01`, `step02`, `step03`... 顺序编号,步骤数根据加工复杂度决定 | 无,按步骤生成 | 标识处理步骤顺序 |
| sql 编排内容解释 | 根据加工逻辑自然语言描述 | 无AI 生成 | 描述该步骤的加工目的和操作 |
| 输入源表 | 从需求文档"所用数据源及字段"表提取 | 无,从输入提取 | 该步骤依赖的源表列表 |
| 加工字段口径 | 从需求文档"输出表字段 - 字段加工逻辑"提取并整理 | 无,从输入提取 | 字段的加工规则说明 |
| 输出表名 | 临时表:`${db_tmp_env}.${temp_table.prefix}${temp_table.suffix}`<br>目标表:`${db_eda_env}.${target_table.prefix}_xxx` | `db_tmp_env`**保留占位符**<br>`db_eda_env`**保留占位符**<br>`temp_table.prefix=tmp_prj004_ads_rpt`**展开为实际值**<br>`temp_table.suffix=_01,_02...`**展开为实际值**<br>`target_table.prefix=prj004_ads_rpt`**展开为实际值** | 最后一步输出目标表,中间步骤输出临时表;**输出时内部变量需展开,外部变量保留** |
| 输出表字段 | 从需求文档"输出表字段"提取,逗号分隔 | 无,从输入提取 | 该步骤产出的字段列表 |
---
### 表 2目标表属性信息 - 字段规则与配置
输出模板字段:`数据目录 | 数据源 | 表编码 | 描述 | 数据源类型 | 表名称 | 数据归口条线 | 是否已上传至集团 | 是否可共享 | 技术人员 | 技术人员电话 | 维护人员 | 维护人员电话 | 管理人员 | 管理人员电话 | 增量/全量 | 是否有敏感信息`
| 输出字段 | 填写规则 | 说明 |
|----------|----------|------|
| 数据目录 | 从需求文档"输出表名 - 数据目录"提取 | 表的业务分类归属 |
| 数据源 | 从需求文档"所用数据源及字段"涉及的源表归纳 | 描述数据来源,如"投诉工单、组织机构" |
| 表编码 | 从需求文档"输出表名 - 表英文名称"提取 | 输出表的英文表名 |
| 描述 | 从需求文档"输出表名 - 表描述"提取 | 表的业务含义描述 |
| 数据源类型 | 固定值:`paimon` | 统一使用 paimon |
| 表名称 | 从需求文档"输出表名 - 表中文名称"提取 | 输出表的中文表名 |
| 数据归口条线 | 从需求文档"需求提出方"提取(如"市场部"→"市场部" | 业务归属部门 |
| 是否已上传至集团 | 固定值:`否` | - |
| 是否可共享 | 固定值:`是` | - |
| 技术人员 | 固定值:`张三` | - |
| 技术人员电话 | 固定值:`13800138000` | - |
| 维护人员 | 固定值:`李四` | - |
| 维护人员电话 | 固定值:`13900139000` | - |
| 管理人员 | 固定值:`王五` | - |
| 管理人员电话 | 固定值:`13700137000` | - |
| 增量/全量 | 根据运行频次自动映射:日/周/月 → `增量`,其他 → `全量` | - |
| 是否有敏感信息 | 固定值:`否` | - |
---
### 表 3目标表字段信息 - 字段规则与配置
输出模板字段:`字段编码 | 字段名称 | 字段类型 | 字段分类 | 排序 | 是否自增 | 是否需脱敏 | 描述`
#### 3.1 各输出字段规则总览
| 输出字段 | 填写规则 | 说明 |
|----------|----------|------|
| 字段编码 | 从需求文档"输出表字段 - 输出字段英文名"提取 | 字段英文名snake_case 格式 |
| 字段名称 | 从需求文档"输出表字段 - 输出字段中文名"提取 | 字段中文名 |
| 字段类型 | 按"3.2 字段类型映射"转换 | 需求类型→数据库类型 |
| 字段分类 | 按"3.3 字段分类规则"自动识别 | 维度/标识/指标/分区 |
| 排序 | 从 1 开始自增 | 连续递增,无跳号 |
| 是否自增 | 固定值:`否` | - |
| 是否需脱敏 | 固定值:`否` | - |
| 描述 | 从需求文档"字段业务含义"+"加工逻辑"整理 | 字段含义 + 加工规则说明 |
#### 3.2 字段类型映射
| 需求文档类型 | 目标数据库类型 | 说明 |
|--------------|----------------|------|
| string | `STRING` | 字符串类型 |
| bigint | `BIGINT` | 大整数 |
| int | `INT` | 整数 |
| timestamp | `TIMESTAMP` | 时间戳 |
| date | `STRING` | 日期(格式 yyyy-MM-dd |
| double/decimal | `DECIMAL(18,2)` | 小数 |
#### 3.3 字段分类规则
| 字段分类 | 识别特征(字段名/业务含义) | 示例 |
|----------|----------------------------|------|
| 标识字段 | 含 `id``code``nbr`、唯一标识类 | `idx_nbr``complaint_id``org_id` |
| 维度字段 | 名称类 (`name`)、类型类 (`type`)、描述类 | `dept_name``bureau_name``complaint_type` |
| 指标字段 | 数值类、统计类、含 `num`/`amt`/`value`/`cnt` | `complaint_accept_num``idx_value` |
| 分区字段 | 含 `day_id``stat_date``dt``date` | `day_id``stat_date` |
#### 3.4 预置字段(可选追加)
**说明**:默认不追加预置字段,目标表字段与需求文档"输出表字段"保持一致。如需追加运维或分区字段,可手动控制:
| 预置类型 | 默认行为 | 说明 |
|----------|----------|------|
| 运维字段 | 不追加 | 如需追加 `etl_time``etl_remark`,手动添加 |
| 分区字段 | 不追加 | 如需追加 `day_id`,手动添加 |
## 处理流程
### Step 1: 解析输入并提取信息
**1.1 输入文件处理**
- 默认:自动从当前目录下的 `ai_text` 文件夹选取最新的 `REQ-DATA-*.md` 文件
- 如果用户指定了文件路径:直接使用该文件
- 如果用户指定了目录路径:从指定目录选取最新的 `REQ-DATA-*.md` 文件
- **不要询问用户选择输入方式**,直接按上述规则自动处理
**1.2 读取并解析需求文档**
读取 MD 文件,从数据需求技术规范文档中提取以下信息:
1. **需求基础信息**:需求编号、需求描述、需求提出方等
2. **输出表信息**:表名(中/英文)、数据目录、表描述
3. **输出字段信息**:字段名、字段类型、加工逻辑、来源表
4. **数据源信息**:库名、表名、字段信息
**1.3 输入/输出目录处理**
- 输入目录:默认使用当前目录下的 `ai_text` 文件夹
- 输出目录:默认使用当前目录下的 `ai_text` 文件夹
- 输入/输出目录无需用户确认,按默认路径处理
**1.4 与用户确认关键信息(多轮对话)**
在生成模型设计文档前,**必须**与用户确认以下关键信息:
```
请确认以下关键信息是否正确:
【需求编号】
- 需求编号:<需求编号>
【输出表信息】
- 目标表名:<表英文名称>
- 表中文名称:<表中文名称>
- 数据目录:<数据目录>
【字段信息】
- 字段总数:<N> 个
- 分区字段:<day_id 等>
- 运行频次:<日/周/月等>
以上信息是否正确?如无误请回复"正确"或"继续",如有问题请指出需要修改的内容。
```
**等待用户确认后才能继续生成。**
**输出文件命名规则**
- 直接从输入文件名转换,将前缀 `REQ` 替换为 `MDDS`Model Design Document Specification
- 输入文件名:`REQ-DATA-YYYYMMDDHHMMSS-XXX.md`
- 输出文件名:`MDDS-DATA-YYYYMMDDHHMMSS-XXX.md`
- 示例:
- 输入文件:`REQ-DATA-20260316142709-001.md`
- 输出文件:`MDDS-DATA-20260316142709-001.md`
### Step 2: 生成编排步骤(对应表 1
**在用户确认关键信息后**,根据字段加工逻辑和数据依赖关系,生成 SQL 编排步骤:
```
编排规则:
1. 识别数据依赖链:来源表 → 中间处理 → 目标表
2. 按处理复杂度拆分步骤,每步一个明确目标:
- 简单场景2 步step01 清洗关联 → step02 补全写入
- 中等场景3~4 步):增加中间聚合、多表关联等步骤
- 复杂场景5~6 步):多层嵌套加工、多次关联转换等
3. 每步骤包含:编排内容解释、输入源表、加工字段口径、输出表名、输出表字段
4. 临时表命名:`${db_tmp_env}.${temp_table.prefix}${temp_table.suffix}`
- `${db_tmp_env}` 保留占位符格式,不展开
- `${temp_table.prefix}` 展开为实际值:`tmp_prj004_ads_rpt`
- `${temp_table.suffix}` 展开为实际值:`_01`, `_02`...
- 示例输出:`${db_tmp_env}.tmp_prj004_ads_rpt_01`
5. 目标表命名:`${db_eda_env}.${target_table.prefix}_xxx`
- `${db_eda_env}` 保留占位符格式,不展开
- `${target_table.prefix}` 展开为实际值:`prj004_ads_rpt`
- `_xxx` 从需求文档"输出表名 - 表英文名称"提取(去掉库名后的表名部分)
- 示例输出:`${db_eda_env}.prj004_ads_rpt_kfb_idx_rt`
```
**字段填写说明**
- 步骤序号:按 `step01`, `step02`, `step03`... 顺序编号,步骤数根据加工复杂度决定
- sql 编排内容解释AI 根据加工逻辑自然语言描述
- 输入源表:从需求文档"所用数据源及字段"表提取
- 加工字段口径:从需求文档"输出表字段 - 字段加工逻辑"提取并整理
- 输出表名:最后一步输出目标表,中间步骤输出临时表
- 临时表格式:`${db_tmp_env}.tmp_prj004_ads_rpt_01``${db_tmp_env}`保留,内部前缀和 suffix 展开)
- 目标表格式:`${db_eda_env}.prj004_ads_rpt_xxx``${db_eda_env}`保留,内部前缀和表名展开)
- 输出表字段:从需求文档"输出表字段"提取,逗号分隔
**编排流程校验(重要)**
生成编排步骤后,**必须**分步进行校验,每步校验后都要等待用户确认:
**第一步:数据依赖链校验**
```
【数据依赖链校验】
请确认以下数据依赖关系是否正确:
| 步骤 | 输入源表 | 来源说明 |
|------|----------|----------|
| step01 | [源表名称] | 外部数据源 |
| step02 | ${db_tmp_env}.tmp_prj004_ads_rpt_01 | step01 输出 |
| ... | ... | ... |
| 最后一步 | ${db_eda_env}.prj004_ads_rpt_xxx | 目标表 |
数据依赖链是否正确?如无误请回复"正确"或"继续",如有问题请指出。
```
**等待用户确认数据依赖链后,再进行第二步校验。**
**第二步:字段流转校验**
```
【字段流转校验】
请确认以下字段流转是否正确:
目标表字段总数N 个
| 字段编码 | 字段名称 | 来源追溯 |
|----------|----------|----------|
| stat_month | 统计月份 | 从 install_dt 提取step01 输入源表) |
| customer_id | 客户 ID | own_cust_idstep01 输入源表) |
| ... | ... | ... |
字段流转是否正确?如无误请回复"正确"或"继续",如有问题请指出。
```
**两步校验都通过后,才能继续生成目标表属性。**
**用户确认环节**
校验通过后,分步向用户展示校验内容:
**第一步:数据依赖链确认**
```
编排步骤已生成,共 N 步:
| 步骤序号 | sql 编排内容解释 | 输入源表 | 加工字段口径 | 输出表名 | 输出表字段 |
|----|----|----|----|----|----|
| step01 | ... | ... | ... | ... | ... |
| step02 | ... | ... | ... | ... | ... |
【数据依赖链校验】
| 步骤 | 输入源表 | 来源说明 |
|------|----------|----------|
| step01 | [源表名称] | 外部数据源 |
| step02 | ${db_tmp_env}.tmp_prj004_ads_rpt_01 | step01 输出 |
| 最后一步 | ${db_eda_env}.prj004_ads_rpt_xxx | 目标表 |
数据依赖链是否正确?如无误请回复"正确"或"继续",如有问题请指出。
```
**等待用户确认数据依赖链后,再进行第二步。**
**第二步:字段流转确认**
```
【字段流转校验】
目标表字段总数N 个
| 字段编码 | 字段名称 | 来源追溯 |
|----------|----------|----------|
| stat_month | 统计月份 | 从 install_dt 提取step01 输入源表) |
| customer_id | 客户 ID | own_cust_idstep01 输入源表) |
| ... | ... | ... |
字段流转是否正确?如无误请回复"正确"或"继续",如有问题请指出。
```
**等待用户确认字段流转后,才能继续生成。**
### Step 3: 生成目标表属性(对应表 2
**在数据依赖链校验和字段流转校验都通过后**,根据需求文档的表信息生成:
```
映射规则:
- 数据目录 ← 需求文档"数据目录"字段
- 数据源 ← 需求文档涉及的源表业务主题(从输入提取)
- 表编码 ← 输出表英文名(从输入提取)
- 描述 ← 需求文档表描述(从输入提取)
- 数据源类型 ← 固定值 "paimon"
- 表名称 ← 输出表中文名称(从输入提取)
- 数据归口条线 ← 需求文档"需求提出方"(如"市场部"→"市场部"
- 是否已上传至集团 ← 固定值 "否"
- 是否可共享 ← 固定值 "是"
- 技术人员/电话 ← 固定值 "张三"/"13800138000"
- 维护人员/电话 ← 固定值 "李四"/"13900139000"
- 管理人员/电话 ← 固定值 "王五"/"13700137000"
- 增量/全量 ← 日/周/月 → "增量",其他 → "全量"
- 是否有敏感信息 ← 固定值 "否"
```
**用户确认环节**
生成目标表属性后,请用户确认:
```
目标表属性信息已生成:
```json
{
"${target_table_name}": {
"data_catalog": "...",
"data_source": "...",
...
}
}
```
以上目标表属性信息是否正确?如无误请回复"正确"或"继续"。
```
**等待用户确认后才能继续生成。**
### Step 4: 生成目标表字段(对应表 3
**在用户确认目标表属性后**,根据需求文档的字段信息进行转换:
```
映射规则:
1. 字段编码 ← 输出字段英文名(从输入提取)
2. 字段名称 ← 输出字段中文名(从输入提取)
3. 字段类型 ← 按"3.2 字段类型映射"转换
4. 字段分类 ← 按"3.3 字段分类规则"自动分类
5. 排序 ← 从 1 开始自增
6. 是否自增 ← 固定值 "否"
7. 是否需脱敏 ← 固定值 "否"
8. 描述 ← 字段业务含义 + 加工逻辑说明AI 整理)
9. 预置字段(默认不追加):
- 运维字段:如需追加 etl_time、etl_remark手动添加
- 分区字段:如需追加 day_id手动添加
```
**最终确认环节**
生成目标表字段后,请用户进行最终确认:
```
目标表字段信息已生成,共 N 个字段:
```json
{
"${target_table_name}": [
{
"field_code": "字段英文名",
"field_name": "字段中文名",
"field_type": "STRING",
"field_category": "维度",
...
}
]
}
```
以上目标表字段信息是否正确?如无误请回复"正确"或"继续"。
```
**等待用户最终确认后,生成并输出完整的模型设计文档。**
---
## 输出模板结构
生成的模型设计文档遵循以下结构:
```markdown
# 模型设计需求模板
## 1. 编排目标表生成流程
| 步骤序号 | sql 编排内容解释 | 输入源表 | 加工字段口径 | 输出表名 | 输出表字段 |
|----|----|----|----|----|----|
| step01 | ... | ... | ... | ... | ... |
## 2. 目标表属性信息
```json
{
"${target_table_name}": {
"data_catalog": "从需求文档提取",
"data_source": "从输入提取",
"table_code": "从输入提取",
"description": "从输入提取",
"data_source_type": "paimon",
"table_name": "从输入提取",
"business_line": "从需求提出方提取",
"is_uploaded": "否",
"is_shareable": "是",
"tech_name": "张三",
"tech_phone": "13800138000",
"maint_name": "李四",
"maint_phone": "13900139000",
"mgr_name": "王五",
"mgr_phone": "13700137000",
"load_type": "增量/全量(根据运行频次映射)",
"has_sensitive": "否"
}
}
```
## 3. 目标表字段信息
```json
{
"${target_table_name}": [
{
"field_code": "字段英文名",
"field_name": "字段中文名",
"field_type": "STRING",
"field_category": "维度",
"sort_order": 1,
"is_auto_increment": "否",
"mask_required": "否",
"description": "字段业务含义和加工规则说明"
}
]
}
```
---
## 版本信息
- **技能版本**v2.4
- **创建日期**2026-04-08
- **更新日期**2026-04-17
- **适用场景**:电信数据仓库 ADS 层模型设计
- **输入模板版本**:数据需求技术规范文档 v5
- **输出模板版本**:模型设计技术规范文档 v1
- **新增功能**
- 默认自动从 `./ai_text/` 目录选取最新需求文档
- 支持目录路径输入,自动选取最新日期的需求文档
- 多轮对话引导,分步确认关键信息
- 默认输出目录为 `./ai_text/`(用户未指定时)
- 编排流程校验分步执行(数据依赖链校验 → 字段流转校验)
---
## 使用示例
### 调用方式
**场景 1默认调用推荐**
技能加载后自动从 `./ai_text/` 目录选取最新的需求文档,无需用户传入参数。
```
用户:加载模型设计技能
→ 技能自动从 ./ai_text/ 选取最新的 REQ-DATA-*.md 文件
```
**场景 2用户传入具体文件路径**
```
用户:请根据 202604/skills_design/v2/data/REQ-DATA-20260409142709-001.md 生成模型设计文档
```
**场景 3用户传入目录路径自动选取最新文件**
```
用户:请根据 202604/skills_design/v2/data/ 目录下的需求文档生成模型设计
```
### 多轮对话流程示例
```
【Step 1】技能解析输入文件后输出关键信息请用户确认
---
请确认以下关键信息是否正确:
【需求编号】
- 需求编号REQ-DATA-20260409142709-001
【输出表信息】
- 目标表名prj004_ads_rpt_kfb_idx_rt
- 表中文名称:投诉处理地投诉受理量表
- 数据目录:上海电信/大数据中心 - 数据安全团队/大数据中心 - 数据安全团队应用层
【字段信息】
- 字段总数34 个
- 分区字段day_id
- 运行频次:日
以上信息是否正确?如无误请回复"正确"或"继续",如有问题请指出需要修改的内容。
---
【用户】正确
【Step 2a】技能生成编排步骤后先进行数据依赖链校验
---
编排步骤已生成,共 2 步:
| 步骤序号 | sql 编排内容解释 | 输入源表 | 加工字段口径 | 输出表名 | 输出表字段 |
|----|----|----|----|----|----|
| step01 | 关联投诉源表与组织架构维表,清洗无效数据 | 投诉源表、组织机构表 | 按部门、类型分组统计 | ${db_tmp_env}.tmp_prj004_ads_rpt_01 | dept_id, dept_name, complaint_type, ... |
| step02 | 补全指标表标准字段格式,写入目标表 | ${db_tmp_env}.tmp_prj004_ads_rpt_01 | 增加统计日期、指标名称等标准字段 | ${db_eda_env}.prj004_ads_rpt_kfb_idx_rt | day_id, stat_date, dept_id, ... |
【数据依赖链校验】
| 步骤 | 输入源表 | 来源说明 |
|------|----------|----------|
| step01 | 投诉源表、组织机构表 | 外部数据源 |
| step02 | ${db_tmp_env}.tmp_prj004_ads_rpt_01 | step01 输出 |
| 最后一步 | ${db_eda_env}.prj004_ads_rpt_kfb_idx_rt | 目标表 |
数据依赖链是否正确?如无误请回复"正确"或"继续",如有问题请指出。
---
【用户】正确
【Step 2b】数据依赖链确认后再进行字段流转校验
---
【字段流转校验】
目标表字段总数34 个
| 字段编码 | 字段名称 | 来源追溯 |
|----------|----------|----------|
| day_id | 统计日期 ID | 从 step01 输入源表提取 |
| stat_date | 统计日期 | 从 step01 输入源表提取 |
| dept_id | 部门 ID | 从 step01 输入源表提取 |
| ... | ... | ... |
字段流转是否正确?如无误请回复"正确"或"继续",如有问题请指出。
---
【用户】正确
【Step 3】技能生成目标表属性后请用户确认
---
目标表属性信息已生成:
```json
{
"prj004_ads_rpt_kfb_idx_rt": {
"data_catalog": "上海电信/大数据中心 - 数据安全团队/大数据中心 - 数据安全团队应用层",
"data_source": "投诉工单、组织机构",
"table_code": "prj004_ads_rpt_kfb_idx_rt",
"description": "投诉处理地投诉受理量表",
"data_source_type": "paimon",
"table_name": "投诉处理地投诉受理量表",
"business_line": "市场部",
"load_type": "增量"
...
}
}
```
以上目标表属性信息是否正确?如无误请回复"正确"或"继续"。
---
【用户】正确
【Step 4】技能生成目标表字段后请用户进行最终确认
---
目标表字段信息已生成,共 34 个字段:
```json
{
"prj004_ads_rpt_kfb_idx_rt": [
{
"field_code": "day_id",
"field_name": "统计日期 ID",
"field_type": "STRING",
"field_category": "分区",
...
}
...
]
}
```
以上目标表字段信息是否正确?如无误请回复"正确"或"继续"。
---
【用户】正确
【最终输出】技能生成完整的模型设计技术规范文档,输出到 ./ai_text/MDDS-DATA-20260409142709-001.md
```
### 输出示例
技能将输出完整的模型设计技术规范文档,包含编排步骤、表属性、字段设计等。
---
## 注意事项
1. **多轮对话确认**:生成过程中必须分步与用户确认关键信息,用户确认后才能继续下一步
- Step 1 后:确认需求编号、输出表信息、字段信息
- Step 2a 后:确认数据依赖链校验
- Step 2b 后:确认字段流转校验
- Step 3 后:确认目标表属性信息
- Step 4 后:确认目标表字段信息
2. **默认输入目录**:默认使用当前目录下的 `ai_text` 文件夹,自动选取最新的 `REQ-DATA-*.md` 文件
- 输入路径示例:`./ai_text/REQ-DATA-20260409142709-001.md`
3. **默认输出目录**:默认使用当前目录下的 `ai_text` 文件夹
- 输出路径示例:`./ai_text/MDDS-DATA-20260409142709-001.md`
- 输入输出目录无需用户确认
4. **编排流程校验**Step 2 生成编排步骤后,必须分两步执行校验
- Step 2a数据依赖链校验逐项展示步骤输入来源用户确认
- Step 2b字段流转校验逐项展示字段来源追溯用户确认
- 两步校验都通过后,才能继续生成目标表属性和字段信息
5. **自动选取最新文件**:按文件名中的 `YYYYMMDDHHMMSS` 部分排序,选取最新的 `REQ-DATA-*.md` 文件
6. **环境变量占位符**:输出表名中的 `${db_tmp_env}` 和 `${db_eda_env}` 需保留占位符格式,运行时由外部环境替换
7. **内部配置变量展开**`${temp_table.prefix}`、`${temp_table.suffix}`、`${target_table.prefix}` 需展开为实际值输出
- `temp_table.prefix` → `tmp_prj004_ads_rpt`
- `temp_table.suffix` → `_01`, `_02`...(按步骤序号)
- `target_table.prefix` → `prj004_ads_rpt`
8. **输出表名示例**
- 临时表正确输出:`${db_tmp_env}.tmp_prj004_ads_rpt_01` ❌ 错误:`${db_tmp_env}.${temp_table.prefix}${temp_table.suffix}`
- 目标表正确输出:`${db_eda_env}.prj004_ads_rpt_kfb_idx_rt` ❌ 错误:`${db_eda_env}.${target_table.prefix}_kfb_idx_rt`
9. **输出文件命名**:直接从输入文件名转换,将 `REQ` 前缀替换为 `MDDS`
- 输入文件:`REQ-DATA-20260316142709-001.md` → 输出文件:`MDDS-DATA-20260316142709-001.md`
10. **字段排序**:从 1 开始连续自增,确保无跳号
11. **命名规范**:所有表名、字段名使用 snake_case 格式
12. **字段一致性**:目标表字段默认与需求文档"输出表字段"保持一致,不追加额外字段
13. **预置字段**:如需追加运维字段 (etl_time、etl_remark) 或分区字段 (day_id),需将对应配置项设为 `true`
---

View File

@@ -0,0 +1,556 @@
---
name: requirement-analyzer
description: 通过分模块多轮对话引导业务人员补充需求信息,自动生成符合《数据需求技术规范文档》标准格式的业务需求模板。根据业务描述智能生成输出表字段。当用户提到数据需求、指标需求、报表需求、需要新建或修改数据表、提到具体的业务指标或数据统计时,必须主动触发此技能。
---
# 需求分析 Skill
## 技能描述
通过**分模块多轮对话**引导业务人员补充需求信息,自动生成符合《数据需求技术规范文档》**标准格式**的业务需求模板。
**核心原则**
- 模板结构固定(表格格式、章节结构严格按规范)
- 字段内容灵活(根据业务描述智能生成,不预设任何字段)
**触发场景**:当用户提到以下任何情况时,必须主动触发此技能:
- 数据需求、指标需求、报表需求、统计需求
- 需要新建或修改数据表、数据仓库、数据指标
- 提到具体的业务指标(如投诉量、受理量、用户数、销售额等)
- 提到数据统计、数据汇总、数据聚合、每日统计、增量更新
- 提到按维度统计(如按日、按部门、按地区、按产品等)
- 需求描述、需求分析、生成需求文档
**重要**:即使用户只是简单提到一个指标想法(如"统计投诉受理量"),也要主动引导用户完成完整的需求分析流程,不要直接给出技术方案。
## 工作流程总览
```
┌─────────────────────────────────────────────────────────────────────┐
│ 数据需求分析完整流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 模块 1+2 │────▶│ 模块 3 │────▶│ 模块 4 │ │
│ │ 基础+详细信息 │ │ 数据源匹配 │ │ 输出表字段生成 │ │
│ │ (同时收集) │ │ (OV优先检索) │ │ (智能生成) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 用户一次性提供 用户确认数据源 用户确认/修改字段 │
│ 基础+详细信息 │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 模块 5 │◀────│ 用户确认 │ │
│ │ 生成文档 │ │ 最终结果 │ │
│ │ (保存文件) │ └──────────────┘ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ 📄 需求文档已保存 │
│ ./ai_text/
│ │
└─────────────────────────────────────────────────────────────────────┘
各模块说明:
┌─────────┬─────────────────────────────────────────────────────────────┐
│ 模块 │ 内容 │
├─────────┼─────────────────────────────────────────────────────────────┤
│ 模块1+2 │ 同时收集:需求编号、描述、提出方、对接人、完成时间、 │
│ │ 需求背景、业务口径、运行频次、备注、输出表信息 │
├─────────┼─────────────────────────────────────────────────────────────┤
│ 模块3 │ 使用 OV 优先匹配数据源,推荐可用表及字段;必要时 fallback 到 legacy data-structure-fetcher │
├─────────┼─────────────────────────────────────────────────────────────┤
│ 模块4 │ 基于确认的数据源,智能生成输出表字段(严禁凭空捏造) │
├─────────┼─────────────────────────────────────────────────────────────┤
│ 模块5 │ 整合所有信息,生成标准格式文档并保存到文件 │
└─────────┴─────────────────────────────────────────────────────────────┘
需求编号规则REQ-DATA-YYYYMMDDHHmmss-XXX
- YYYYMMDDHHmmss当前服务器精确时间年4位+月2位+日2位+时2位+分2位+秒2位
- XXX3位序号从001开始
- 示例REQ-DATA-20260410162709-0012026年04月10日 16:27:09
```
---
## 模块 1+2需求基础信息 + 详细信息(同时收集)
**目标**:一次性收集需求基础信息和详细业务信息,减少交互轮次
**引导问题**
```
好的,我来帮您生成标准的需求文档。为了高效完成,请您一次性提供以下信息:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第一部分:基础信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 需求描述(核心业务目标是什么?)
2. 需求提出方/人(哪个部门/谁提出的?)
3. 需求对接人(业务对接人和技术对接人分别是谁?)
4. 期望完成时间YYYY-MM-DD 格式示例2026-04-30
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第二部分:详细信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5. 需求背景(为什么要做这个需求?业务场景说明)
6. 业务口径描述(详细的数据处理逻辑,包括统计维度、统计指标、过滤条件等)
7. 运行频次(日/周/月/准实时,示例:日)
8. 备注信息(如数据合并规则、目标表等)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第三部分:输出表信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9. 输出表中文名称(这张结果表的中文名是什么?)
10. 输出表英文名/路径(库名.表名格式)
11. 数据目录(如:上海电信/大数据中心-数据安全团队/大数据中心-数据安全团队应用层)
12. 数据源类型paimon/hive/mysql 等)
13. 表的业务用途描述
```
**智能引导**(根据用户回答自动追问):
- **统计维度**:确认所有分组维度(如:按日、按部门、按地区、产品类型等)
- **统计指标**:确认每个指标的计算方式(如:计数、求和、去重计数、平均值等)
- **数据更新方式**:全量覆盖 vs 增量追加(如"每日增量更新"需确认增量字段)
- **时间范围**:历史数据回溯要求、数据保留周期
- **过滤条件**:有效数据筛选规则(如:仅统计已处理的投诉、排除测试数据等)
**输出格式**
```markdown
【模块 1+2 完成】需求基础信息 + 详细信息
### 基础信息
| 序号 | 项目 | 填写内容 | 必填标识 | 备注 |
|------|------|----------|----------|------|
| 1 | 需求编号 | REQ-DATA-{YYYYMMDDHHmmss}-{XXX} | | 唯一标识,按**服务器当前精确时间**(精确到秒)+ 序号编排 |
| 2 | 需求描述 | {value} | ★ | 简洁概括核心业务目标 |
| 3 | 需求提出方/人 | {value} | ★ | 业务归属部门/人 |
| 4 | 需求对接人 | {value} | ★ | 业务 + 技术对接责任人 |
| 5 | 期望完成时间 | {value} | | 需求落地完成时限 |
### 详细信息
| 项目 | 内容 | 必填标识 |
|------|------|----------|
| 需求背景 | {value} | ★ |
| 业务口径描述 | {value} | ★ |
| 运行频次 | {value} | ★ |
| 备注信息 | {value} | ★ |
### 输出表信息
| 输出属性 | 表中文名称 | 表英文名称(库+表) | 数据目录 | 数据源类型 | 表描述 |
|---------|-----------|---------------------|---------|-----------|--------|
| 表新建/表更新 | {表中文名称} | {库名.表名} | {数据目录} | {数据源类型} | {表描述} |
以上信息是否正确?确认后进入模块 3OV 优先匹配数据源)。
```
**需求编号生成规则**
- 格式:`REQ-DATA-YYYYMMDDHHmmss-XXX`14 位日期时间 +3 位序号)
- 示例:`REQ-DATA-20260410162709-001`
- 规则:
- 日期时间:**必须获取服务器当前实际时间**年4位+月2位+日2位+时2位+分2位+秒2位共14位
- 序号3 位,从 001 开始
- 示例拆解:`20260410162709` = 2026年04月10日 16:27:09
- ⚠️ **重要**:时间必须精确到秒,不可使用估算时间或示例时间
---
## 模块 3OV 优先匹配数据源
**目标**:使用 OV 搜索字段处理经验、表结构、历史成功 SQL 和业务语义,推荐可用数据源。`legacy-data-structure-fetcher` 只作为用户同意后的 fallback。
**调用方式**
1. 提取模块 1+2 的业务口径描述关键词
2. 运行任何 OV 命令之前,先读取 `../references/ov-search-context.md` 了解 OV 命令用法
3. 先搜索 `field-process-memory`,用历史经验判断常用数据源、业务口径和候选方向:
- `conda run -n py13 ov find "{业务口径/核心实体}" --uri "viking://resources/field-process-memory" --threshold 0.2 --node-limit 10`
- `conda run -n py13 ov grep "{核心实体|关键词}" --uri "viking://resources/field-process-memory" --node-limit 10`
4. 再用 `table-metadata` 补充确认表结构和字段:
- `conda run -n py13 ov find "{业务口径/核心实体}" --uri "viking://resources/table-metadata" --threshold 0.2 --node-limit 10`
- `conda run -n py13 ov grep "{核心实体|关键词}" --uri "viking://resources/table-metadata" --node-limit 10`
5. 对每个推荐表按顺序执行 OV 深度检索:
- 常用数据源判断:`conda run -n py13 ov grep "{表名}" --uri "viking://resources/field-process-memory" --node-limit 10`
- 字段处理经验:`conda run -n py13 ov grep "{表名}.*{字段名}" --uri "viking://resources/field-process-memory" --node-limit 10`
- 业务语义:`conda run -n py13 ov find "{表名} {业务场景}" --uri "viking://resources/field-process-memory" --threshold 0.2 --node-limit 10`
- 表结构:`conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata" --node-limit 10`
- 成功 SQL`conda run -n py13 ov grep "{表名}" --uri "viking://resources/sql_snippets" --node-limit 15`
6. 获取推荐的数据源列表(库、表、字段、技术负责人、字段类型),并给出常用数据源判断
7. 仅当 OV 命令失败、候选表不足、或用户明确要求旧方案时,先告知用户原因并获得明确同意;同意后再读取 `../references/legacy-data-structure-fetcher/SKILL.md` 并使用其中脚本补充推荐
**legacy fallback 同意模板**
```text
OV 检索结果不足/不可用。是否同意我使用 legacy data-structure-fetcher 脚本补充搜索数据源?
```
**输出格式**
```markdown
【模块 3】数据源匹配结果
根据您的需求描述,我先使用 OV 搜索字段处理经验和历史记忆,再补充搜索表结构与历史 SQL推荐以下数据源
### 推荐数据源(按综合得分排序)
| 库 | 表 | 表中文描述 | 字段 | 字段中文描述 | 技术负责人 | 字段类型 |
|----|----|------------|------|--------------|------------|----------|
| {schema} | {table} | {table_cn_name} | {column} | {column_cn} | {owner} | {type} |
| {schema} | {table} | {table_cn_name} | {column} | {column_cn} | {owner} | {type} |
请确认:
- 选择哪些表作为数据源?(回复序号或表名)
- 需要补充其他表吗?
- 技术负责人信息是否正确?
回复"数据源确认"进入模块 4生成输出表字段
```
### 常用数据源判断格式
```markdown
📋 常用数据源判断结果:
【{schema}.{table}】
- 检索结果:找到 {N} 条历史使用记录
- 判断:✅ 常用数据源 / ⚠️ 偶尔使用 / ❓ 新或陌生数据源 / ⚠️ 有风险
- 历史使用场景:{场景摘要}
- 注意事项:{字段处理经验或风险}
```
---
## 模块 4智能生成输出表字段
**目标**:基于模块 1+2 的业务描述 + 模块 3 的数据源结果,智能生成输出表字段
**⚠️ 核心约束(必须遵守)**
1. **严禁凭空捏造字段**:输出字段的"来源表.字段"必须严格来自模块 3 用户确认的数据源表及其字段,不得编造不存在的表或字段
2. **严禁凭空调用代码**:不得假设存在某个 API、脚本或数据库连接所有加工逻辑必须基于模块 3 已确认的源表字段
3. **字段映射必须有依据**:每个输出字段的加工逻辑必须清晰说明如何从模块 3 的源字段转换/聚合/计算得到
**智能生成逻辑**
1. **回顾模块 3 数据源**:列出用户确认的所有数据源表及其字段清单
2. **解析业务口径**:从模块 1+2 提取统计维度、统计指标、过滤条件
3. **维度字段生成**:按统计维度生成字段,来源必须指向模块 3 确认的源表字段
- 例如:若统计维度是"投诉处理地部门",则需找到模块 3 中哪个表的哪个字段代表部门(如 `dwd_crm_srv_complaint_rt_pos.create_org_id`
- 若需要部门名称,需关联模块 3 中的组织架构表(如 `dwd_crm_bas_organization_ext_rt_ff.org_id``accnt_bureau`
4. **指标字段生成**:按统计指标生成字段,来源必须指向模块 3 确认的源表字段
- 例如:投诉受理量 = `count(distinct dwd_crm_srv_complaint_rt_pos.complaint_id)`
5. **技术字段生成**根据需要添加技术字段如数据写入时间、分区字段、etl_remark 等)
- 系统函数字段(如 `etl_time``day_id`)来源标注为"系统函数"或"系统参数"
- 固定赋值字段(如指标编码、指标名称)来源标注为"固定值"
6. **填充加工逻辑**:根据业务口径描述生成字段加工逻辑,明确标注来源表.字段
7. **建议负责人**:从模块 1+2 的需求对接人中提取
**输出格式**
```markdown
【模块 4】输出表字段自动生成初稿
根据您的业务描述和数据源匹配结果,我生成了以下输出字段:
### 输出表名
| 输出属性* | 表中文名称* | 表英文名称(库+表) | 数据目录* | 数据源类型* | 表描述 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| {表新建/表更新} | {表中文名称} | {库名.表名} | {数据目录} | {数据源类型} | {表描述} |
### 输出表字段
| 排序 | 输出字段中文名* | 输出字段英文名 | 字段业务含义* | 输出字段类型 | 字段加工逻辑(多个)* | 来源表.字段(多个)* | 口径业务负责人* | 口径技术负责人* | 说明 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 1 | {字段中文名} | {field_name} | {业务含义} | {type} | {加工逻辑} | {source_table.column} | {业务负责人} | {技术负责人} | {说明} |
| 2 | {字段中文名} | {field_name} | {业务含义} | {type} | {加工逻辑} | {source_table.column} | {业务负责人} | {技术负责人} | {说明} |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
请检查并修改:
- 字段是否完整?需要补充或删除哪些字段?
- 字段名称是否需要调整?
- 字段加工逻辑是否准确?来源表.字段是否正确?
- 负责人信息是否正确?
确认后可修改,回复"字段确认"进入模块 5。
```
**用户修改机制**
- 用户可以直接指出需要修改的字段
- 用户可以补充新字段
- 用户可以删除字段
- 修改后重新生成表格供确认
**字段生成注意事项**
- **来源表.字段必须有依据**:每个字段的"来源表.字段"列必须严格来自模块 3 用户确认的数据源,不得编造
- 对于无业务含义的扩展字段(如 x_dim01~x_dim10加工逻辑标注为"赋值为 null",来源表标注为"-"
- 对于固定赋值字段(如指标编码、指标名称),加工逻辑标注为"固定赋值:'xxx'",来源表标注为"固定值"
- 对于系统函数字段(如 etl_time、day_id加工逻辑标注为系统函数如 now()、${day_id}),来源表标注为"系统函数"或"系统参数"
- 字段加工逻辑应清晰描述业务规则,如部门名称映射规则、统计计算方式等
- **加工逻辑示例**
- 维度字段:`关联 dwd_crm_bas_organization_ext_rt_ff.org_id = dwd_crm_srv_complaint_rt_pos.create_org_id取 accnt_bureau 作为部门名称`
- 指标字段:`按日期+部门分组count(distinct dwd_crm_srv_complaint_rt_pos.complaint_id)`
---
## 模块 5生成完整需求文档模板
**目标**:整合所有模块信息,生成标准格式的需求文档并保存到文件
**文档保存**
- 保存路径:`./ai_text/`
- 文件命名:`REQ-DATA-{YYYYMMDDHHmmss}-{XXX}.md`(与需求编号一致)
- 时间要求:**必须使用当前实际时间**年4位+月2位+日2位+时2位+分2位+秒2位
- 示例:`./ai_text/REQ-DATA-20260410162709-001.md`2026年04月10日 16:27:09
- 保存方式:使用 write 工具将完整文档写入该路径
**文档结构**(严格按此顺序):
1. 需求基础信息
2. 需求详细信息
3. 所用数据源及字段
4. 确认输出表名及字段
**输出格式**
```markdown
# 数据需求技术规范文档
## 1. 需求基础信息
| 序号 | 项目 | 填写内容 | 必填标识 | 备注 |
|------|------|----------|----------|------|
| 1 | 需求编号 | REQ-DATA-{YYYYMMDDHHmmss}-{XXX} | | 唯一标识,按**当前实际时间**(精确到秒)+序号编排 |
| 2 | 需求描述 | {需求描述} | ★ | 简洁概括核心业务目标 |
| 3 | 需求提出方/人 | {需求提出方/人} | ★ | 业务归属部门/人 |
| 4 | 需求对接人 | {需求对接人} | ★ | 业务+技术对接责任人 |
| 5 | 期望完成时间 | {期望完成时间} | | 需求落地完成时限 |
---
## 2. 需求详细信息
| 项目 | 内容 | 必填标识 |
|------|------|----------|
| 需求背景 | {需求背景} | ★ |
| 业务口径描述 | {业务口径描述} | ★ |
| 运行频次 | {运行频次} | ★ |
| 备注信息 | {备注信息} | ★ |
---
## 3. 所用数据源及字段
| 库 | 表 | 表中文描述 | 字段 | 字段中文描述 | 技术负责人 | 字段类型 |
|----|----|------------|------|--------------|------------|----------|
{数据源行,每张表的每个字段一行}
---
## 4. 确认输出表名及字段
### 输出表名
| 输出属性* | 表中文名称* | 表英文名称(库+表) | 数据目录* | 数据源类型* | 表描述 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| {表新建/表更新} | {表中文名称} | {库名.表名} | {数据目录} | {数据源类型} | {表描述} |
### 输出表字段
| 排序 | 输出字段中文名* | 输出字段英文名 | 字段业务含义* | 输出字段类型 | 字段加工逻辑(多个)* | 来源表.字段(多个)* | 口径业务负责人* | 口径技术负责人* | 说明 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
{输出表字段行}
---
```
**完成提示**
```
✅ 需求文档已生成并保存!
📄 文件路径:./ai_text/{需求编号}.md
文档包含:
- 第 1 块需求基础信息5 项)
- 第 2 块需求详细信息4 项)+ 输出表信息
- 第 3 块:所用数据源及字段({M}张表,{N}个字段)
- 第 4 块:确认输出表名及字段({K}个字段)
如需修改任何内容,请指出具体模块和字段。
```
---
## 交互规则
1. **模块1+2合并引导**:基础信息和详细信息在同一轮对话中同时向用户收集,减少交互轮次
2. **分模块推进**:按 模块1+2 → 模块3 → 模块4 → 模块5 顺序执行,每模块完成后需用户确认
3. **智能校验**:检查必填项是否完整,格式是否正确
4. **数据源优先**:模块 3 先读取 OV reference再按 `field-process-memory``table-metadata``sql_snippets` 顺序匹配数据源,模块 4 再基于数据源生成输出字段legacy data-structure-fetcher 只在 OV 不可用、召回不足或用户指定且用户明确同意后使用
5. **文档保存**:模块 5 生成文档后,必须使用 write 工具将文档保存到 `./ai_text/{需求编号}.md`
6. **灵活跳转**:允许用户随时补充或修改之前模块的信息
7. **多轮对话记忆**:记住用户之前提供的信息,避免重复询问
8. **字段加工逻辑**:必须清晰描述业务规则,不能仅写"待确认"
9. **需求编号时间**:必须使用服务器当前精确时间(精确到秒),不可使用估算或示例时间
## 状态管理
使用以下状态变量跟踪进度:
```
state = {
"current_module": "1+2",
"module1_2_complete": false,
"module3_complete": false,
"module4_complete": false,
"module5_complete": false,
"data": {
"基础信息": {...},
"详细信息": {...},
"输出表信息": {...},
"数据源": [...],
"输出表字段": [...]
}
}
```
## 触发指令
用户输入包含以下关键词时触发:
- "数据需求"、"需求分析"、"生成需求文档"
- "指标需求"、"报表需求"、"需求描述"
- "统计 XX 量"、"按 XX 维度"
- "每日/每周/每月更新"
- "新建表"/"修改表"、"数据口径"
## 示例对话
**用户**:我有一个数据需求,要统计各投诉处理部门的投诉量
**助手**:好的,我来帮您生成标准的需求文档。为了高效完成,请您一次性提供以下信息:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第一部分:基础信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 需求描述(核心业务目标是什么?)
2. 需求提出方/人(哪个部门/谁提出的?)
3. 需求对接人(业务对接人和技术对接人分别是谁?)
4. 期望完成时间YYYY-MM-DD 格式)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第二部分:详细信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5. 需求背景(为什么要做这个需求?业务场景说明)
6. 业务口径描述(详细的数据处理逻辑)
7. 运行频次(日/周/月/准实时)
8. 备注信息(如数据合并规则、目标表等)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 第三部分:输出表信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
9. 输出表中文名称
10. 输出表英文名/路径(库名.表名格式)
11. 数据目录
12. 数据源类型paimon/hive/mysql 等)
13. 表的业务用途描述
**用户**:需求是监控各投诉处理部门的服务压力与绩效表现。提出方是市场部流量运营组/张三,业务对接人李四,技术对接人王五,期望 2026-04-30 完成。背景是实时监控各部门服务压力。口径是按日统计各投诉处理部门的投诉受理量,每日增量更新。备注:'市场部卡中心(投诉用)'等统一命名为"市场部",数据插入到 db_eda_prj004_prd.prj004_ads_rpt_kfb_idx_rt。表名投诉处理地投诉受理量表数据目录上海电信/大数据中心-数据安全团队/大数据中心-数据安全团队应用层,数据源类型 paimon。
**助手**:收到,确认基础信息 + 详细信息:
### 基础信息
| 序号 | 项目 | 填写内容 | 必填标识 | 备注 |
|------|------|----------|----------|------|
| 1 | 需求编号 | REQ-DATA-{当前服务器精确时间YYYYMMDDHHmmss}-{XXX} | | 唯一标识示例20260410162709 = 2026年04月10日 16:27:09 |
| 2 | 需求描述 | 监控各投诉处理部门的服务压力与绩效表现,统计投诉受理量 | ★ | 核心业务目标 |
| 3 | 需求提出方/人 | 市场部流量运营组/张三 | ★ | 业务归属 |
| 4 | 需求对接人 | 李四(业务)、王五(技术) | ★ | 对接责任人 |
| 5 | 期望完成时间 | 2026-04-30 | | 完成时限 |
### 详细信息
| 项目 | 内容 | 必填标识 |
|------|------|----------|
| 需求背景 | 实时监控各部门服务压力与绩效表现 | ★ |
| 业务口径描述 | 按日统计各投诉处理部门的投诉受理量,每日增量更新 | ★ |
| 运行频次 | 日 | ★ |
| 备注信息 | '市场部卡中心(投诉用)'等统一命名为"市场部",数据插入到 db_eda_prj004_prd.prj004_ads_rpt_kfb_idx_rt | ★ |
### 输出表信息
| 输出属性 | 表中文名称 | 表英文名称(库+表) | 数据目录 | 数据源类型 | 表描述 |
|---------|-----------|---------------------|---------|-----------|--------|
| 表更新 | 投诉处理地投诉受理量表 | db_eda_prj004_prd.prj004_ads_rpt_kfb_idx_rt | 上海电信/大数据中心-数据安全团队/大数据中心-数据安全团队应用层 | paimon | 按处理地维度统计投诉受理量相关指标 |
以上信息是否正确?确认后进入模块 3。
**用户**:正确
**助手**:✅ 模块 1+2 完成
【模块 3】数据源匹配
正在使用 OV 搜索表结构、历史 SQL 和字段处理经验...
推荐以下数据源:
| 库 | 表 | 表中文描述 | 字段 | 字段中文描述 | 技术负责人 | 字段类型 |
|----|----|------------|------|--------------|------------|----------|
| db_dwd | dwd_crm_srv_complaint_rt | 投诉整合层主表 | complaint_id | 投诉ID | 蒋平川 | STRING |
| db_dwd | dwd_crm_srv_complaint_rt | 投诉整合层主表 | assist_dept_org_id | 发展组织id | 蒋平川 | STRING |
| db_dwd | dwd_crm_srv_complaint_rt | 投诉整合层主表 | create_date | 创建日期 | 蒋平川 | STRING |
| db_dim | dim_intnl_org_new | 组织架构维表 | org_id | 组织id | 范鑫鑫 | STRING |
| db_dim | dim_intnl_org_new | 组织架构维表 | org_name | 组织名称 | 范鑫鑫 | STRING |
请确认选择哪些表作为数据源?
**用户**:选择全部,数据源确认
**助手**:✅ 模块 3 完成
【模块 4】输出表字段自动生成初稿
根据您的业务描述和数据源匹配结果,我生成了以下输出字段:
### 输出表名
| 输出属性* | 表中文名称* | 表英文名称(库+表) | 数据目录* | 数据源类型* | 表描述 |
| ---- | ---- | ---- | ---- | ---- | ---- |
| 表更新 | 投诉处理地投诉受理量表 | db_eda_prj004_prd.prj004_ads_rpt_kfb_idx_rt | 上海电信/大数据中心-数据安全团队/大数据中心-数据安全团队应用层 | paimon | 按处理地维度统计投诉受理量相关指标 |
### 输出表字段
| 排序 | 输出字段中文名* | 输出字段英文名 | 字段业务含义* | 输出字段类型 | 字段加工逻辑(多个)* | 来源表.字段(多个)* | 口径业务负责人* | 口径技术负责人* | 说明 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 1 | 指标编码 | idx_nbr | 投诉量指标唯一编码 | string | 固定赋值:'D_DXT_00002085' | 固定值 | 李四 | 王五 | 指标唯一标识 |
| 2 | 指标名称 | idx_name | 指标中文名称 | string | 固定赋值:'投诉量' | 固定值 | 李四 | 王五 | 指标业务名称 |
| 3 | 投诉处理部门名称 | bureau_name | 标准化后的投诉处理部门名称 | string | 1. 原始部门名称为'市场部卡中心(投诉用)'、'市场部终端中心(投诉用)'、'市场部综合(投诉用)'时,统一替换为"市场部",其他部门名称保持原值。<br>2. 关联组织架构维表dim_intnl_org_new获取组织名称org_name为投诉处理地部门 | dwd_crm_srv_complaint_rt.assist_dept_org_id; dim_intnl_org_new.org_id; dim_intnl_org_new.org_name | 李四 | 王五 | 部门名称标准化口径 |
| 4 | 投诉受理量 | idx_value | 当日单部门投诉受理总数量 | bigint | 按日期+部门分组count(distinct complaint_id)统计投诉条数 | dwd_crm_srv_complaint_rt.complaint_id | 李四 | 王五 | 投诉总量统计 |
| 5 | 指标值类型 | idx_val_type | 指标值数据类型标识 | string | 判断idx_value是否为整数是则赋值'整数',否则'小数' | 计算字段idx_value | 李四 | 王五 | 本需求固定为整数 |
| 6 | 数据加工时间 | etl_time | 数据写入表的时间 | timestamp | 系统当前时间now() | 系统函数now() | - | 王五 | 数据加工追溯使用 |
| 7 | 统计日期 | day_id | 投诉数据统计日期 | string | 1. 取${day_id}转换为yyyy-MM-dd格式<br>2. 作为分区键,每日覆盖写入 | 系统参数${day_id} | 李四 | 王五 | 分区字段格式yyyy-MM-dd |
请检查并修改,回复"字段确认"进入模块 5。
**用户**:字段确认
**助手**:✅ 模块 4 完成
【模块 5】生成完整需求文档
正在生成标准格式的需求文档并保存到文件...
📄 已保存至:`./ai_text/REQ-DATA-20260410162709-001.md`
✅ 需求文档已生成!
文档包含:
- 第 1 块需求基础信息5 项)
- 第 2 块需求详细信息4 项)+ 输出表信息
- 第 3 块所用数据源及字段2 张表5 个字段)
- 第 4 块确认输出表名及字段7 个字段)
如需修改任何内容,请指出具体模块和字段。

View File

@@ -0,0 +1,130 @@
---
name: write-sql
description: 编写多引擎 SQL。当用户需要写 SQL、数据查询、数据处理、ETL、数据转换、数据聚合、窗口函数、多表关联、数据仓库相关任务时使用此技能。
---
# Role
你是一个精通 SQL 的数据库专家,支持多种引擎。你的唯一任务是根据给定的上下文,编写符合目标引擎规范的精确 SQL 语句。
# 支持引擎
| 引擎 | 标识 | 适用场景 |
|------|------|----------|
| Spark SQL | `spark`(默认) | 批处理、ETL、Paimon 数据仓库 |
| Apache Doris | `doris` | 实时 OLAP 分析、报表查询 |
| Hive | `hive` | 离线批处理、历史数据仓库 |
| Kudu (Impala) | `kudu` | 实时更新、UPSERT、点查 |
# Inputs
write-sql 不自己读取文件,所有输入通过参数传入:
| 参数 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `engine` | 否 | `spark` | 目标引擎spark / doris / hive / kudu |
| `context` | **是** | - | SQL 生成的上下文(需求描述、模型设计、业务逻辑等,直接传入文本内容) |
| `output_path` | 否 | 无 | 输出文件路径(由调用方决定,通常为 `./ai_text/` 下)。若提供则写入文件,否则仅在对话中展示 |
# 核心流程
```
1. 确定 engine默认 spark
2. 解析 context判断任务类型
3. SQL 编写前先查 OV 记忆和经验:
→ 源表/字段结构 → viking://resources/table-metadata
→ 字段处理经验/口径 → viking://resources/field-process-memory
→ 历史成功 SQL/代码参考 → viking://resources/sql_snippets
4. 精确加载一个对应模板:
→ ETL/数据开发 → ../references/sql/templates/{engine}/etl-template.sql
→ 查询/取数 → ../references/sql/templates/{engine}/query-template.sql
→ 建表 → ../references/sql/templates/{engine}/create-table-template.sql
→ 数据插入 → ../references/sql/templates/{engine}/insert-template.sql
→ 分区操作(仅Spark) → ../references/sql/templates/spark/partition-template.sql
5. 加载引擎生成规则:
→ ../references/sql/reference/{engine}-sql-syntax.md 的「SQL 生成规则」章节
6. 按 OV 结果 + 通用规则 + 引擎特有规则 + 模板骨架生成 SQL
7. 如有 output_path 则写入文件
```
# OV 记忆和经验优先
写 SQL 前必须先检查 OV 中的记忆、经验和历史代码,尤其是找源表、确认字段、需要代码参考或遇到不确定点时。不要凭经验猜表结构、字段含义、口径处理或 SQL 写法。
## 必查资源
| 资源 | 用途 | 命令示例 |
|------|------|----------|
| `viking://resources/table-metadata` | 表结构、字段定义、字段类型 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata" --node-limit 10` |
| `viking://resources/field-process-memory` | 字段处理经验、常用数据源判断、业务口径经验 | `conda run -n py13 ov grep "{表名}.*{字段名}" --uri "viking://resources/field-process-memory" --node-limit 10` |
| `viking://resources/sql_snippets` | 历史成功 SQL、代码片段、相似场景写法 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/sql_snippets" --node-limit 15` |
## 困惑场景立即检索
| 场景 | 动作 |
|------|------|
| 表名陌生或源表不确定 | 先查 `table-metadata`,再查 `field-process-memory` 判断是否常用 |
| 字段含义、字段类型、关联键不确定 | 查 `table-metadata` 的表名+字段名组合 |
| 字段处理口径不确定 | 查 `field-process-memory` 的表名+字段名组合 |
| 需要参考历史代码或相似 SQL | 查 `sql_snippets` 的表名、业务场景、关键语法 |
| 引擎语法不确定 | 先读 `../references/sql/reference/{engine}-sql-syntax.md`,再查 `sql_snippets` 里的成功案例 |
## OV 结果使用方式
- 把 OV 找到的源表、字段、关联键、处理经验写进 SQL 生成依据。
- 如果 OV 结果与 context 冲突,先指出冲突并请用户确认,不能静默选择。
- 如果 OV 没有结果,明确说明未找到相关记忆,并退回到 context + 本地 SQL reference/templates 生成。
# 通用规则(所有引擎都必须遵守)
以下规则适用于所有引擎,不受引擎差异影响:
1. **禁止使用 CTE (WITH 子句)**,每个主要逻辑步骤必须物化为临时表
2. **先 DROP 再 CREATE**`DROP TABLE IF EXISTS ...; CREATE TABLE ... AS SELECT ...;`
3. **禁止 `SELECT *`**,必须明确列出所有字段
4. 多表查询时所有表必须使用简短别名
5. 每个步骤前添加注释说明
6. **谓词下推**过滤条件前置JOIN 时在 WHERE 中一并添加过滤
7. 临时表命名:`${db_tmp_env}.tmp_{业务简称}_{步骤序号}`
# 引擎特有规则
**各引擎的特有规则DML差异、函数差异、写入方式等定义在本 skill 的 `../references/sql/reference/` 文件中。**
生成 SQL 时,**必须查阅** `../references/sql/reference/{engine}-sql-syntax.md` 的以下章节:
- **SQL 生成规则**:该引擎特有的约束和规范
- **与 Spark SQL 的主要差异**:语法和函数的对照
- **DML 差异**INSERT/UPDATE/DELETE/UPSERT 支持情况
# Output Format
## 对话输出
在对话中展示完整的 SQL 脚本,用 ` ```sql ` 代码块包裹。
## 文件输出(当调用方提供 output_path 时)
文件路径由调用方(如 smart-data-developer决定和传入write-sql 不自己定义路径规则。
写入文件时,**必须只包含纯 SQL 脚本内容**
- **禁止** Markdown 格式(标题、表格、分隔线)
- **禁止** 代码块标记(` ```sql `` ``` `
- **只允许** SQL 注释(`--`)和 SQL 语句
# Workflow
1. **确认引擎**:根据 `engine` 参数确定目标引擎(默认 spark
2. **判断任务类型**:从 context 判断是 ETL / 查询 / 建表 / 插入
3. **先查 OV 记忆和经验**:根据 context 中的源表、字段、业务场景检索 `table-metadata``field-process-memory``sql_snippets`
4. **精确加载一个模板**:只加载任务类型对应的那个模板文件,不要加载多个
5. **查阅引擎规则**:读取 `../references/sql/reference/{engine}-sql-syntax.md` 的「SQL 生成规则」章节
6. **解析 context + OV 结果**提取表名、字段、逻辑、过滤条件、聚合需求、JOIN 关系和历史处理经验
7. **生成 SQL**OV 结果 + 通用规则 + 引擎特有规则 + 模板骨架
8. **自我审查**:检查是否遗漏步骤、是否符合规范
9. **输出**:对话展示 SQL + 如调用方提供了 output_path 则写入纯 SQL 文件
# 硬性约束
1. **不读取文件**write-sql 不负责读取需求文档或模型设计文档,所有内容通过 context 传入
2. **不决定路径**文件输出路径由调用方决定write-sql 只负责写入
3. **必须查阅本 skill 的 SQL 资源**:生成 SQL 前先查阅 `../references/sql/reference/``../references/sql/templates/`
4. **必须先查 OV 记忆和经验**:找源表、查字段、参考历史代码、处理困惑时先查 `table-metadata``field-process-memory``sql_snippets`
5. **不编造语法**:不确定的语法查阅 reference 和 OV 成功案例确认

View File

@@ -0,0 +1,105 @@
---
name: data-structure-fetcher
description: 通过文本实体匹配→向量检索→大模型加权汇总三步流程,从数据字典库中智能匹配业务需求所需的数据源及字段。当用户需要了解数据结构、查找表字段、查询数据字典或询问有哪些数据可用时,使用此技能。
---
# 数据结构获取 Skill
> Legacy fallback: 本文件只在 OV 不可用、OV 召回不足,或用户明确要求旧数据字典匹配脚本时使用。使用前必须告知用户原因并获得明确同意。默认优先使用 `../ov-search-context.md` 中的 OV 搜索。
## 技能描述
采用**三步匹配流程**,直接调用脚本获取数据源:
1. **向量检索**:调用 `embedding_api_call.py`,传入用户完整问题
2. **文本实体匹配**:从问题中提取核心实体,对**每个实体**依次调用 `wenben_api_call.py`
3. **加权汇总**:解析两份结果 → 加权融合(向量 0.6 + 文本 0.4)→ 输出推荐 Top 5 表
## 目录结构
```
data-structure-fetcher/
├── SKILL.md
└── scripts/
├── embedding_api_call.py # 向量检索脚本
└── wenben_api_call.py # 文本匹配脚本
```
## 使用方法
### 步骤 1提取查询实体
从用户问题中提取核心业务实体/关键词。例如:
- "统计各投诉处理部门的投诉受理量" → 提取:"投诉"、"部门"、"受理量"
### 步骤 2调用向量检索一次
```bash
conda run -n my_opencode python ./references/legacy-data-structure-fetcher/scripts/embedding_api_call.py "用户完整问题" 10
```
### 步骤 3调用文本匹配每个实体依次调用
```bash
conda run -n my_opencode python ./references/legacy-data-structure-fetcher/scripts/wenben_api_call.py "实体1" 5
conda run -n my_opencode python ./references/legacy-data-structure-fetcher/scripts/wenben_api_call.py "实体2" 5
# ... 每个实体依次调用
```
**注意**:所有实体的返回结果需**合并去重**,作为文本匹配的总结果。
### 步骤 4解析脚本返回结果
两个脚本均返回 **Markdown 表格格式**,需从中提取结构化信息:
- **向量检索结果**:包含 `distance` 字段(相似度,越小越匹配)
- **文本匹配结果**:不包含 `distance` 字段
每个结果包含:
- `ID`:库名.表名(如 `db_dwd.dwd_crm_srv_complaint_rt`
- `元数据`JSON 格式,含 `schema_name``table_name``table_cn_name``table_comment``table_owner``field_count`
- `文档内容`:字段清单,格式如 `字段名(类型)、字段中文名、...`
### 步骤 5加权融合排序
```
综合得分 = 向量归一化得分 × 0.6 + 文本归一化得分 × 0.4
```
- **向量得分**`score = 1 - distance`,然后 min-max 归一化到 [0,1]
- **文本得分**:按命中顺序归一化(首次出现得分最高)
- **来源标记**
- `both` — 两个源都命中(★ 最高置信度)
- `vector_only` — 仅向量命中(□)
- `text_only` — 仅文本命中(□)
### 步骤 6输出 Top 5 推荐表
```
================================================================================
📊 推荐数据源 Top 5
================================================================================
★ 1. db_dwd.dwd_crm_srv_complaint_rt
中文名:投诉整合层主表
表注释:投诉业务整合宽表
综合得分0.9200
来源both (向量=0.950, 文本=0.875)
负责人:蒋平川
□ 2. db_dwa.dwa_crm_base_sr_compln_3rd
中文名:投诉三级延伸全量宽表
综合得分0.8000
来源vector_only (向量=0.820, 文本=0.000)
负责人:郭鑫超
================================================================================
```
## 触发指令
- "查找相关表"、"推荐数据源"、"这个指标用哪些表"
- "查询数据结构"、"有哪些表可以统计 XX"
- "需要了解数据结构"、"查找表字段"、"查询数据字典"
## 被 requirement-analyzer 调用
`requirement-analyzer` 进入模块 3 且 OV 不可用、召回不足或用户指定旧方案时,先询问用户是否同意使用 legacy fallback用户同意后才按上述流程执行脚本并汇总结果。

View File

@@ -0,0 +1,124 @@
import sys
import requests
import json
import os
# =========================
# 1. 配置
# =========================
# 这里填写你刚才启动的 Flask 服务地址
# 如果是本机运行,通常是 http://127.0.0.1:5001
# 如果是服务器远程访问,请替换为服务器的 IP 地址
API_URL = "http://127.0.0.1:5001/search"
def query_table_metadata(query_text, top_k=10):
"""
调用 Flask 接口并返回 Markdown 格式的字符串
Args:
query_text (str or list): 查询内容,可以是字符串或字符串列表
top_k (int): 返回结果数量
Returns:
str: 格式化的 Markdown 字符串
"""
# 1. 准备请求数据
payload = {
"q": query_text,
"top_k": top_k
}
headers = {
"Content-Type": "application/json"
}
print(f"🔍 正在查询: '{query_text}' ...")
try:
# 2. 发送 POST 请求
# 设置 timeout 防止接口卡死
response = requests.post(API_URL, json=payload, headers=headers, timeout=10)
# 检查 HTTP 状态码
if response.status_code != 200:
return f"❌ 请求失败,状态码: {response.status_code}, 错误信息: {response.text}"
# 3. 解析 JSON 响应
res_json = response.json()
if res_json.get("status") != "success":
return f"❌ 接口返回错误: {res_json.get('error')}"
data_list = res_json.get("data", [])
# 4. 生成 Markdown 内容
md_output = generate_markdown(data_list)
return md_output
except requests.exceptions.ConnectionError:
return "❌ 连接错误:无法连接到 Flask 服务,请检查 IP 地址和端口,或确认服务是否启动。"
except requests.exceptions.Timeout:
return "❌ 请求超时:服务器响应时间过长。"
except Exception as e:
return f"❌ 发生未知错误: {str(e)}"
def generate_markdown(data_list):
"""
将接口返回的数据列表转换为 Markdown 表格
"""
if not data_list:
return "⚠️ 未查询到任何数据。"
md_lines = []
# 遍历每一个查询结果(支持批量查询)
for item in data_list:
query = item.get("query")
results = item.get("results", [])
# 添加查询标题
md_lines.append(f"### 🔎 查询结果:{query}")
md_lines.append(f"共找到 {len(results)} 条相关表信息:\n")
# 添加表头
md_lines.append("| ID | 相似度 (Distance) | 元数据 (Metadata) | 文档内容 (Document) |")
md_lines.append("| :--- | :---: | :--- | :--- |")
# 添加每一行数据
for res in results:
doc_id = res.get("id", "N/A")
distance = res.get("distance")
# 格式化距离保留4位小数
dist_str = f"{distance:.4f}" if distance is not None else "N/A"
metadata = res.get("metadata", {})
# 将元数据字典转为字符串去除单引号以便Markdown显示更整洁
meta_str = json.dumps(metadata, ensure_ascii=False)
document = res.get("document", "")
# 简单的清洗,防止文档内容里的换行符破坏表格结构
document = document.replace("\n", " ").replace("|", "/")
md_lines.append(f"| {doc_id} | {dist_str} | {meta_str} | {document} |")
md_lines.append("\n---\n") # 分割线
return "\n".join(md_lines)
# =========================
# 5. 主程序入口 (测试用)
# =========================
if __name__ == "__main__":
# 获取命令行参数,例如: python client.py "查询内容" 10
if len(sys.argv) > 1:
query_input = sys.argv[1]
k_input = int(sys.argv[2]) if len(sys.argv) > 2 else 5
else:
# 默认值
query_input = "按日分投诉"
k_input = 10
result_md = query_table_metadata(query_input, top_k=k_input)
print(result_md)

View File

@@ -0,0 +1,121 @@
import sys
import requests
import json
import os
# =========================
# 1. 配置
# =========================
# 这里填写你刚才启动的 Flask 服务地址
# 如果是本机运行,通常是 http://127.0.0.1:5001
# 如果是服务器远程访问,请替换为服务器的 IP 地址
API_URL = "http://127.0.0.1:5001/like"
def query_table_metadata(query_text, top_k=5):
"""
调用 Flask 接口并返回 Markdown 格式的字符串
Args:
query_text (str or list): 查询内容,可以是字符串或字符串列表
top_k (int): 返回结果数量
Returns:
str: 格式化的 Markdown 字符串
"""
# 1. 准备请求数据
payload = {
"q": query_text,
"top_k": top_k
}
headers = {
"Content-Type": "application/json"
}
print(f"🔍 正在查询: '{query_text}' ...")
try:
# 2. 发送 POST 请求
# 设置 timeout 防止接口卡死
response = requests.post(API_URL, json=payload, headers=headers, timeout=10)
# 检查 HTTP 状态码
if response.status_code != 200:
return f"❌ 请求失败,状态码: {response.status_code}, 错误信息: {response.text}"
# 3. 解析 JSON 响应
res_json = response.json()
if res_json.get("status") != "success":
return f"❌ 接口返回错误: {res_json.get('error')}"
data_list = res_json.get("data", [])
# 4. 生成 Markdown 内容
md_output = generate_markdown(data_list)
return md_output
except requests.exceptions.ConnectionError:
return "❌ 连接错误:无法连接到 Flask 服务,请检查 IP 地址和端口,或确认服务是否启动。"
except requests.exceptions.Timeout:
return "❌ 请求超时:服务器响应时间过长。"
except Exception as e:
return f"❌ 发生未知错误: {str(e)}"
def generate_markdown(data_list):
"""
将接口返回的数据列表转换为 Markdown 表格
"""
if not data_list:
return "⚠️ 未查询到任何数据。"
md_lines = []
# 遍历每一个查询结果(支持批量查询)
for item in data_list:
query = item.get("query")
results = item.get("results", [])
# 添加查询标题
md_lines.append(f"### 🔎 查询结果:{query}")
md_lines.append(f"共找到 {len(results)} 条相关表信息:\n")
# 添加表头
md_lines.append("| ID | 元数据 (Metadata) | 文档内容 (Document) |")
md_lines.append("| :--- | :--- | :--- |")
# 添加每一行数据
for res in results:
doc_id = res.get("\ufeffid", "N/A")
metadata = res.get("metadata", {})
# 将元数据字典转为字符串去除单引号以便Markdown显示更整洁
meta_str = json.dumps(metadata, ensure_ascii=False)
document = res.get("document", "")
# 简单的清洗,防止文档内容里的换行符破坏表格结构
document = document.replace("\n", " ").replace("|", "/")
md_lines.append(f"| {doc_id} | {meta_str} | {document} |")
md_lines.append("\n---\n") # 分割线
return "\n".join(md_lines)
# =========================
# 5. 主程序入口 (测试用)
# =========================
if __name__ == "__main__":
# 获取命令行参数,例如: python client.py "查询内容" 10
if len(sys.argv) > 1:
query_input = sys.argv[1]
k_input = int(sys.argv[2]) if len(sys.argv) > 2 else 5
else:
# 默认值
query_input = "投诉"
k_input = 5
result_md = query_table_metadata(query_input, top_k=k_input)
print(result_md)

View File

@@ -0,0 +1,157 @@
---
name: ov-search-context
description: Search context data(memories, skills and resource) from OpenViking Context Database (aka. ov). Trigger this tool when 1. need information that might be stored as memories, skills or resources on OpenViking; 2. is explicitly requested searching files or knowledge; 3. sees `search context`, `search openviking`, `search ov` request.
compatibility: CLI configured at `~/.openviking/ovcli.conf`
---
# OpenViking (OV) context searching
The `ov search` command performs context-aware retrieval across all memories and resources in OpenViking — combining semantic understanding with directory recursive retrieval to find the most relevant context for any query.
## Table of Content
- When to Use
- Sub-commands for search
- List directories (`ov ls`)
- Tree view (`ov tree`)
- Semantic Search (`ov find`)
- Content Pattern Search (`ov grep`)
- File Glob Search (`ov glob`)
- Full content read (`ov read`)
- Get overview (`ov overview`)
- Get Abstract (`ov abstract`)
- Prerequisite
## When to Use
- Finding specific information within imported resources or saved memories
- Retrieving context about topics, APIs, or patterns previously added
- Searching across project documentation, code, and learnings
- When an agent needs to reference previously stored knowledge
> note: cli command can be outdated, when sees error, use `--help` to get latest usage
## Sub-commands for search
### List Contents (`ov ls`)
Browse directory structure:
```bash
# List root directory
ov ls
# List specific directory
ov ls viking://resources/my-project/docs/
# Simple path output (only uris, no metadata)
ov ls viking://resources --simple
# Show hidden files
ov ls viking://resources --all
# Control output limits (default 256)
ov ls viking://resources --node-limit 50
# Control abstract info length limit for each node (default 256)
ov ls viking://resources --abs-limit 128
```
### Tree View (`ov tree`)
Visualize directory hierarchy:
```bash
# Show tree structure
ov tree viking://resources
# Control depth limits (default 3)
ov tree viking://resources --level-limit 2
# Control node limits
ov tree viking://resources --node-limit 100 --abs-limit 128
# Show all files including hidden
ov tree viking://resources --all
```
### Semantic find (`ov find`)
Find method with semantic relevance ranking:
```bash
# Basic find across all context
ov find "how to handle API rate limits"
# Find within specific URI scope
ov find "authentication flow" --uri "viking://resources/my-project"
# Limit results and set relevance score threshold
ov find "error handling" --node-limit 5 --threshold 0.3
```
### Content Pattern Search (`ov grep`)
Literal pattern matching:
```bash
# Find exact text pattern (Note: this is expensive, and suggest within specific small URI scope)
ov grep "viking://resources" "TODO:" --uri "viking://resources/my-project"
# Case-insensitive search
ov grep "viking://resources" "API_KEY" --ignore-case --uri "viking://resources/my-project"
# Limit results and set node limit
ov grep "viking://resources" "API_KEY" --node-limit 5 --uri "viking://resources/my-project"
```
### File Glob Search (`ov glob`)
File path pattern matching:
```bash
# Find all markdown files (Note: this is expensive, and suggest within specific small URI scope)
ov glob "**/*.md" --uri "viking://resources/my-project"
# Limit results and set node limit
ov glob "**/*.md" --uri "viking://resources/my-project" --node-limit 5
```
### Read File Content (`ov read`)
Retrieve full content (L0-L2 layer):
```bash
# Read full content
ov read viking://resources/docs/api/api-1.md
# Read first 10 lines of api-2.md
ov read viking://resources/docs/api/api-2.md | head -n 10
# Read abstract (L0 - quick summary)
ov abstract viking://resources/docs/api/
ov read viking://resources/docs/api/.abstract.md
# Read overview (L1 - key points)
ov overview viking://resources/docs/api/
ov read viking://resources/docs/api/.overview.md
```
### Combining Search
Use search results to guide further actions:
```bash
ov ls viking://resources/
# Search for relevant files
ov search "authentication" --uri "viking://resources/project-A"
# Get overview for context
ov overview viking://resources/project-A/backend
# Decide to read specific content
ov read viking://resources/project-A/backend/auth.md
```
## Prerequisites
- CLI configured: `~/.openviking/ovcli.conf`
- Resources or memories previously added to OpenViking

View File

@@ -0,0 +1,459 @@
# 聚合模式速查
## 基本聚合
### 聚合函数列表
| 函数 | 说明 | 示例 |
|------|------|------|
| COUNT(*) | 计数含NULL行 | 总行数 |
| COUNT(col) | 计数不含NULL | 有效数据数 |
| COUNT(DISTINCT col) | 去重计数 | 用户数 |
| SUM(col) | 求和 | 总销售额 |
| AVG(col) | 平均值 | 平均薪资 |
| MIN(col) | 最小值 | 最小年龄 |
| MAX(col) | 最大值 | 最高分 |
### 基本用法
```sql
-- 单列聚合
SELECT
COUNT(*) AS total_rows,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
MIN(amount) AS min_amount,
MAX(amount) AS max_amount
FROM orders
-- 分组聚合
SELECT
department,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary
FROM employees
GROUP BY department
```
---
## GROUP BY
```sql
-- 单字段分组
SELECT
category,
COUNT(*) AS count
FROM products
GROUP BY category
-- 多字段分组
SELECT
category,
brand,
COUNT(*) AS count,
SUM(price) AS total_price
FROM products
GROUP BY category, brand
-- 分组 + 排序
SELECT
department,
COUNT(*) AS count
FROM employees
GROUP BY department
ORDER BY count DESC
-- 分组 + LIMIT取Top N组
SELECT
category,
COUNT(*) AS count
FROM products
GROUP BY category
ORDER BY count DESC
LIMIT 10
```
---
## HAVING分组过滤
```sql
-- HAVING vs WHERE
-- WHERE过滤原始行GROUP BY 前)
-- HAVING过滤分组结果GROUP BY 后)
-- 示例筛选订单数大于10的用户
SELECT
user_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING COUNT(*) > 10
-- 多条件 HAVING
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 5000
AND COUNT(*) >= 5
-- HAVING + ORDER BY
SELECT
user_id,
COUNT(*) AS order_count
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 5
ORDER BY order_count DESC
```
---
## 去重计数
```sql
-- COUNT(DISTINCT)
SELECT
COUNT(DISTINCT user_id) AS unique_users
FROM orders
-- 分组去重计数
SELECT
date,
COUNT(DISTINCT user_id) AS unique_users,
COUNT(*) AS total_orders
FROM orders
GROUP BY date
-- 多字段去重计数
SELECT
COUNT(DISTINCT user_id, product_id) AS unique_user_product_pairs
FROM order_items
-- 大数据量近似去重(性能优化)
SELECT
approx_count_distinct(user_id) AS approx_unique_users
FROM orders
```
---
## 集合聚合(数组结果)
```sql
-- collect_list收集为数组不去重
SELECT
user_id,
collect_list(product_id) AS products
FROM orders
GROUP BY user_id
-- collect_set收集为数组去重
SELECT
user_id,
collect_set(product_id) AS unique_products
FROM orders
GROUP BY user_id
-- 取数组大小
SELECT
user_id,
size(collect_list(product_id)) AS product_count,
size(collect_set(product_id)) AS unique_product_count
FROM orders
GROUP BY user_id
```
---
## 多级聚合ROLLUP / CUBE / GROUPING SETS
### ROLLUP层级汇总
```sql
-- 从右到左递减分组级别
SELECT
COALESCE(year, '总计') AS year,
COALESCE(month, '全年') AS month,
COALESCE(region, '全国') AS region,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY ROLLUP (year, month, region)
-- 结果包含:
-- 1. year + month + region 分组
-- 2. year + month 汇总region为NULL
-- 3. year 汇总month,region为NULL
-- 4. 全表汇总year,month,region为NULL
```
### CUBE全维度组合
```sql
-- 所有分组组合
SELECT
COALESCE(year, '总计') AS year,
COALESCE(month, '全月') AS month,
COALESCE(region, '全国') AS region,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY CUBE (year, month, region)
-- 结果包含所有组合:
-- year+month+region, year+month, year+region, month+region
-- year, month, region, 全表汇总
```
### GROUPING SETS自定义组合
```sql
-- 指定分组组合
SELECT
year,
month,
region,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY GROUPING SETS (
(year, month),
(year, region),
(region),
()
)
-- 等价于多个 GROUP BY 合并
```
---
## GROUPING 函数(判断汇总级别)
```sql
-- GROUPING(col)判断该列是否为汇总产生的NULL
-- 0 = 真实值, 1 = 汇总NULL
SELECT
year,
month,
SUM(sales) AS total_sales,
GROUPING(year) AS is_year_total,
GROUPING(month) AS is_month_total
FROM sales_data
GROUP BY ROLLUP (year, month)
-- 用 GROUPING 区分真实NULL和汇总NULL
SELECT
CASE WHEN GROUPING(region) = 1 THEN '全国汇总' ELSE region END AS region,
SUM(sales) AS total_sales
FROM sales_data
GROUP BY ROLLUP (region)
```
---
## 条件聚合CASE WHEN + 聚合)
```sql
-- 分条件统计
SELECT
date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count
FROM orders
GROUP BY date
-- 分条件求和
SELECT
department,
SUM(salary) AS total_salary,
SUM(CASE WHEN gender = 'M' THEN salary ELSE 0 END) AS male_salary,
SUM(CASE WHEN gender = 'F' THEN salary ELSE 0 END) AS female_salary
FROM employees
GROUP BY department
-- 条件平均值
SELECT
category,
AVG(CASE WHEN price > 100 THEN price ELSE NULL END) AS high_price_avg
FROM products
GROUP BY category
```
---
## 聚合 + 窗口函数
```sql
-- 分组内占比
SELECT
department,
salary,
ROUND(salary / SUM(salary) OVER (PARTITION BY department) * 100, 2) AS salary_pct
FROM employees
-- 分组累计
SELECT
date,
department,
amount,
SUM(amount) OVER (PARTITION BY department ORDER BY date) AS cumulative
FROM sales
-- 分组排名
SELECT
*,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS salary_rank
FROM employees
```
---
## 多表聚合
```sql
-- JOIN 后聚合
SELECT
u.department,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.department
-- 子查询聚合
SELECT
dept_stats.department,
dept_stats.avg_salary,
emp_count.employee_count
FROM (
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department
) dept_stats
JOIN (
SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department
) emp_count ON dept_stats.department = emp_count.department
```
---
## 常见问题
### 问题1GROUP BY 字段遗漏
```sql
-- 错误SELECT 字段不在 GROUP BY 中
SELECT
department,
name, -- 错误name 未分组
AVG(salary)
FROM employees
GROUP BY department
-- 解决1添加到 GROUP BY
SELECT
department,
name,
AVG(salary)
FROM employees
GROUP BY department, name
-- 解决2使用聚合函数处理
SELECT
department,
collect_list(name) AS names, -- 收集所有name
AVG(salary)
FROM employees
GROUP BY department
```
### 问题2NULL 影响聚合
```sql
-- COUNT(*) 包含 NULL 行
-- COUNT(col) 不包含 NULL
SELECT
COUNT(*) AS total, -- 包含 NULL 行
COUNT(amount) AS valid, -- 不包含 amount 为 NULL 的行
COUNT(DISTINCT amount) AS unique_values
FROM orders
-- SUM/AVG/MIN/MAX 自动忽略 NULL
SELECT AVG(price) FROM products -- NULL 自动排除
```
### 问题3聚合结果精度
```sql
-- AVG 可能精度丢失
SELECT
AVG(amount) AS avg_amount -- 可能精度不够
AVG(CAST(amount AS DECIMAL(18,6))) AS precise_avg -- 高精度
FROM orders
-- ROUND 控制精度
SELECT
ROUND(AVG(amount), 2) AS avg_amount
FROM orders
```
---
## 聚合性能优化
```sql
-- 1. 先过滤再聚合
SELECT
department,
COUNT(*) AS count
FROM employees
WHERE hire_date >= '2024-01-01' -- 先过滤
GROUP BY department
-- 2. 大数据量用近似聚合
SELECT
approx_count_distinct(user_id) AS users -- 比 COUNT(DISTINCT) 快
FROM orders
-- 3. 减少分组字段数量
SELECT
category, -- 减少分组字段
COUNT(*) AS count
FROM products
GROUP BY category -- 比 GROUP BY category, brand 快
-- 4. 避免复杂计算在 GROUP BY 前
SELECT
department,
AVG(salary * 1.1) AS adjusted_avg -- 先计算再聚合
FROM employees
GROUP BY department
```
---
## 聚合模式选择指南
| 需求 | 推荐方式 |
|------|----------|
| 简单统计 | GROUP BY + 聚合函数 |
| 分条件统计 | CASE WHEN + SUM/COUNT |
| 去重计数 | COUNT(DISTINCT) |
| 大数据去重 | approx_count_distinct |
| 收集数组 | collect_list / collect_set |
| 层级汇总 | ROLLUP |
| 全维度汇总 | CUBE |
| 自定义组合 | GROUPING SETS |
| 分组内计算 | 窗口函数 |
| 多条件过滤 | HAVING |

View File

@@ -0,0 +1,320 @@
# Apache Doris SQL 语法参考
## 数据类型
| 类型 | 说明 | 示例 |
|------|------|------|
| BOOLEAN | 布尔 | active BOOLEAN |
| TINYINT | 1字节整数 | level TINYINT |
| SMALLINT | 2字节整数 | age SMALLINT |
| INT | 4字节整数 | count INT |
| BIGINT | 8字节整数 | id BIGINT |
| LARGEINT | 16字节整数 | hash_key LARGEINT |
| FLOAT | 4字节浮点 | score FLOAT |
| DOUBLE | 8字节浮点 | price DOUBLE |
| DECIMAL(p,s) | 定点数 | amount DECIMAL(18,2) |
| DATE | 日期 | birth_date DATE |
| DATETIME | 日期时间(精确到秒) | created_at DATETIME |
| CHAR(n) | 定长字符串 | code CHAR(10) |
| VARCHAR(n) | 变长字符串 | name VARCHAR(100) |
| STRING | 变长字符串(无长度限制) | description STRING |
| BITMAP | 位图(精确去重) | user_bitmap BITMAP |
| HLL | HyperLogLog近似去重 | user_hll HLL |
| JSON | JSON 数据 | props JSON |
| ARRAY\<type\> | 数组 | tags ARRAY\<STRING\> |
| MAP\<k,v\> | 映射 | props MAP\<STRING,STRING\> |
| STRUCT\<field:type,...\> | 结构体 | info STRUCT\<id:INT,name:STRING\> |
---
## 时间函数
```sql
-- 当前时间
NOW() -- 当前日期时间
CURDATE() -- 当前日期
CURRENT_TIMESTAMP() -- 当前时间戳
-- 格式转换
DATE_FORMAT(date_col, '%Y-%m-%d') -- 日期格式化
DATE_FORMAT(datetime_col, '%Y-%m-%d %H:%i:%s') -- 时间格式化
STR_TO_DATE(str, '%Y-%m-%d') -- 字符串转日期
-- 日期计算
DATE_ADD(date_col, INTERVAL 7 DAY) -- 加7天
DATE_SUB(date_col, INTERVAL 7 DAY) -- 减7天
DATEDIFF(end_date, start_date) -- 日期差(天数)
TIMESTAMPDIFF(unit, start, end) -- 时间差(指定单位)
TIMESTAMPADD(unit, interval, datetime) -- 时间加
-- 日期提取
YEAR(date_col) -- 年
MONTH(date_col) -- 月
DAY(date_col) -- 日
HOUR(datetime_col) -- 时
MINUTE(datetime_col) -- 分
SECOND(datetime_col) -- 秒
QUARTER(date_col) -- 季度 (1-4)
WEEK(date_col) -- 年中第几周
DAYOFWEEK(date_col) -- 周几 (1=周日)
DAYOFYEAR(date_col) -- 年中第几天
-- Unix 时间戳
UNIX_TIMESTAMP() -- 当前 Unix 时间戳
UNIX_TIMESTAMP(datetime_col) -- 转换为 Unix 时间戳
FROM_UNIXTIME(timestamp) -- Unix 时间戳转时间字符串
FROM_UNIXTIME(timestamp, fmt) -- 带格式的转换
-- Doris 日期格式符(不同于 Spark
-- %Y: 4位年, %m: 2位月, %d: 2位日
-- %H: 24小时制, %i: 分钟, %s: 秒
-- %j: 年中天数, %W: 周名
```
---
## 字符串函数
```sql
-- 常用函数
CONCAT(str1, str2, ...) -- 字符串拼接
CONCAT_WS('-', str1, str2, ...) -- 用分隔符拼接
LOWER(str) -- 转小写
UPPER(str) -- 转大写
TRIM(str) -- 去两端空格
LTRIM(str) -- 去左空格
RTRIM(str) -- 去右空格
LENGTH(str) -- 字符串长度
CHAR_LENGTH(str) -- 字符数(中文友好)
SUBSTRING(str, pos, len) -- 截取字符串pos从1开始
LEFT(str, len) -- 取左边len个字符
RIGHT(str, len) -- 取右边len个字符
REVERSE(str) -- 反转字符串
REPEAT(str, n) -- 重复n次
SPACE(n) -- 生成n个空格
-- 查找与替换
INSTR(str, substr) -- 查找子串位置
LOCATE(substr, str, pos) -- 从pos位置查找
REPLACE(str, old, new) -- 替换
REGEXP_EXTRACT(str, pattern, idx) -- 正则提取(注意和 Spark 参数不同)
REGEXP_REPLACE(str, pattern, replacement) -- 正则替换
-- 分割
SPLIT_BY_STRING(str, delimiter) -- 分割Doris 特有)
SPLIT_PART(str, delimiter, idx) -- 取分割后的第idx部分
-- 其他
INITCAP(str) -- 首字母大写
LPAD(str, len, pad) -- 左填充
RPAD(str, len, pad) -- 右填充
HEX(str) -- 转16进制
UNHEX(str) -- 16进制转字符串
```
---
## 聚合函数
```sql
-- 基础聚合
COUNT(*) -- 计数含NULL行
COUNT(col) -- 计数不含NULL
COUNT(DISTINCT col) -- 去重计数
SUM(col) -- 求和
AVG(col) -- 平均值
MIN(col) -- 最小值
MAX(col) -- 最大值
-- 集合聚合
GROUP_CONCAT(col SEPARATOR ',') -- 字符串聚合(类似 collect_list
APPROX_COUNT_DISTINCT(col) -- 近似去重计数
-- 统计函数
VARIANCE(col) -- 方差
VAR_POP(col) -- 总体方差
VAR_SAMP(col) -- 样本方差
STDDEV(col) -- 标准差
STDDEV_POP(col) -- 总体标准差
STDDEV_SAMP(col) -- 样本标准差
-- Bitmap 精确去重Doris 特有)
-- 用于 Aggregate Key 模型中定义为 BITMAP 的列
-- bitmap_union_count(bitmap_col) -- 精确去重计数
-- bitmap_union(bitmap_col) -- 合并 bitmap
-- bitmap_hash(col) -- 将值转为 bitmap用于写入
-- HLL 近似去重Doris 特有)
-- hll_union_agg(hll_col) -- 近似去重计数
-- hll_cardinality(hll_col) -- 返回 HLL 基数
-- hll_hash(col) -- 将值转为 HLL用于写入
```
---
## 条件表达式
```sql
-- CASE WHEN
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
-- CASE 字段匹配
CASE field
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
-- COALESCE取第一个非空值
COALESCE(col1, col2, default_value)
-- NULLIF相等返回NULL
NULLIF(col1, col2)
-- IF简单条件
IF(condition, true_value, false_value)
-- IFNULL空值替换等同 NVL
IFNULL(col, default_value)
```
---
## JSON 函数
```sql
-- 解析与提取
JSON_PARSE(json_str) -- 解析 JSON 字符串
JSON_EXTRACT(json_str, '$.field') -- 提取 JSON 字段(返回 JSON 类型)
JSON_EXTRACT_STRING(json_str, '$.field') -- 提取 JSON 字段(返回 STRING
JSON_EXTRACT_INT(json_str, '$.field') -- 提取 JSON 字段(返回 INT
JSON_EXTRACT_DOUBLE(json_str, '$.field') -- 提取 JSON 字段(返回 DOUBLE
JSON_EXTRACT_BOOL(json_str, '$.field') -- 提取 JSON 字段(返回 BOOLEAN
-- 路径语法
-- $ : 根节点
-- $.field : 对象字段
-- $[0] : 数组索引
-- $.a.b : 嵌套字段
-- 构造
JSON_OBJECT('key1', val1, 'key2', val2) -- 构造 JSON 对象
JSON_ARRAY(val1, val2, ...) -- 构造 JSON 数组
-- 查询
JSON_LENGTH(json_str) -- JSON 长度
JSON_KEYS(json_str) -- JSON 所有 key
JSON_VALID(json_str) -- 是否合法 JSON
```
---
## ARRAY 函数
```sql
-- 创建
ARRAY(val1, val2, ...) -- 创建数组
-- 访问
array_contains(arr, val) -- 判断是否包含
element_at(arr, idx) -- 取元素idx从1开始
arr[idx] -- 取元素idx从0开始
-- 操作
SIZE(arr) -- 数组长度
ARRAY_JOIN(arr, delimiter) -- 数组转字符串
CONCAT(arr1, arr2) -- 数组拼接
-- 展开与排序
EXPLODE(arr) -- 展开数组为多行LATERAL VIEW 中使用)
ARRAY_SORT(arr) -- 排序
ARRAY_DISTINCT(arr) -- 去重
```
---
## 与 Spark SQL 的主要差异
| 特性 | Spark SQL | Apache Doris | 说明 |
|------|-----------|-------------|------|
| **日期格式符** | `yyyy-MM-dd` | `%Y-%m-%d` | Doris 用 MySQL 风格格式符 |
| **当前时间** | `current_timestamp()` | `NOW()``CURRENT_TIMESTAMP()` | 都支持Doris 偏好 NOW() |
| **日期加减** | `date_add(col, 7)` | `DATE_ADD(col, INTERVAL 7 DAY)` | Doris 需要 INTERVAL 语法 |
| **正则提取** | `regexp_extract(str, pattern, idx)` | `REGEXP_EXTRACT(str, pattern, idx)` | 参数名可能不同 |
| **LEFT SEMI JOIN** | ✅ 支持 | ❌ 不支持 | 用 IN 子查询替代 |
| **LEFT ANTI JOIN** | ✅ 支持 | ❌ 不支持 | 用 NOT IN / NOT EXISTS 替代 |
| **INSERT OVERWRITE** | ✅ 支持 | ✅ 2.0+ 支持 | 早期版本不支持 |
| **CTE (WITH)** | ✅ 支持 | ✅ 支持 | 都支持 |
| **MERGE INTO** | ✅ 支持 | ❌ 不支持 | Doris 用 UPSERT 替代 |
| **临时表链式处理** | ✅ 推荐 | ✅ 统一规范 | 编码规范层面统一禁止 CTE |
| **精确去重** | COUNT(DISTINCT) | COUNT(DISTINCT) 或 BITMAP | Doris BITMAP 性能更好 |
| **近似去重** | `approx_count_distinct` | HLL 类型 | Doris 内置 HLL 类型 |
| **UPDATE/DELETE** | ❌ 不支持 | ✅ 支持 | Doris 支持 DML |
| **UPSERT** | ❌ 不支持 | ❌ 不直接支持 | 用 Aggregate/Unique Key 模型替代 |
| **分页** | LIMIT无OFFSET | LIMIT + OFFSET | Doris 完整支持 |
| **JSON** | `get_json_object` | `JSON_EXTRACT_STRING` 等系列函数 | 函数名完全不同 |
| **collect_list/set** | ✅ 支持 | ❌ 不直接支持 | 用 GROUP_CONCAT 替代 |
| **LATERAL VIEW explode** | ✅ 支持 | ✅ 支持 | 语法兼容 |
---
## SQL 生成规则
### 通用规则(所有引擎统一)
1. **禁止使用 CTE (WITH 子句)**,每个主要逻辑步骤必须物化为临时表
2. **先 DROP 再 CREATE**`DROP TABLE IF EXISTS ...; CREATE TABLE ... AS SELECT ...;`
3. **禁止 `SELECT *`**,必须明确列出所有字段
4. 多表查询时所有表必须使用简短别名
5. 每个步骤前添加注释说明
6. **谓词下推**过滤条件前置JOIN 时在 WHERE 中一并添加过滤
7. 临时表命名:`${db_tmp_env}.tmp_{业务简称}_{步骤序号}`
### Doris 特有规则
1. 使用 `INSERT INTO` 写入目标表Doris 2.0+ 也支持 `INSERT OVERWRITE`
2. Aggregate Key 表自动合并相同 Key 的数据
3. Unique Key 表自动按主键去重,保留最新数据
4. 不支持 `LEFT SEMI JOIN` / `LEFT ANTI JOIN`,用 `IN` / `NOT IN` 替代
5. 精确去重推荐 `BITMAP`,近似去重推荐 `HLL`
6. 日期函数用 MySQL 风格:
- `DATE_FORMAT(col, '%Y-%m-%d')`(不是 `yyyy-MM-dd`
- `DATE_ADD(col, INTERVAL 7 DAY)`(不是 `date_add(col, 7)`
- `CURDATE()` / `NOW()`
7. 不支持 `collect_list` / `collect_set`,用 `GROUP_CONCAT` 替代
8. 时间范围筛选:
```sql
-- 日账期过滤
WHERE stat_date = '${day_id}'
-- 最近N个月
WHERE stat_date >= DATE_FORMAT(DATE_SUB(STR_TO_DATE('${month_id}', '%Y%m'), INTERVAL N MONTH), '%Y%m')
AND stat_date < '${month_id}'
```
### SQL 脚本结构
```sql
-- =====================================================================
-- @SqlName: doris-D-SQL-{表名}
-- @Engine: doris
-- ...(头注释)
-- =====================================================================
-- Step01: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT ...;
-- Step02: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT ...;
-- 最后一步:写入目标表
INSERT INTO ${db_eda_env}.target_table
SELECT ...;
```

View File

@@ -0,0 +1,334 @@
# Hive SQL 语法参考
## 数据类型
| 类型 | 说明 | 示例 |
|------|------|------|
| TINYINT | 1字节整数 | level TINYINT |
| SMALLINT | 2字节整数 | age SMALLINT |
| INT | 4字节整数 | count INT |
| BIGINT | 8字节整数 | id BIGINT |
| FLOAT | 4字节浮点 | score FLOAT |
| DOUBLE | 8字节浮点 | price DOUBLE |
| DECIMAL(p,s) | 定点数 | amount DECIMAL(18,2) |
| BOOLEAN | 布尔 | active BOOLEAN |
| STRING | 变长字符串 | name STRING |
| VARCHAR(n) | 变长字符串(限长) | code VARCHAR(50) |
| CHAR(n) | 定长字符串 | flag CHAR(1) |
| DATE | 日期 | birth_date DATE |
| TIMESTAMP | 时间戳(纳秒精度) | created_at TIMESTAMP |
| BINARY | 二进制 | data BINARY |
| ARRAY\<type\> | 数组 | tags ARRAY\<STRING\> |
| MAP\<k,v\> | 映射 | props MAP\<STRING,STRING\> |
| STRUCT\<f1:t1,...\> | 结构体 | user STRUCT\<id:INT,name:STRING\> |
| UNIONTYPE\<t1,t2,...\> | 联合类型 | value UNIONTYPE\<INT,STRING\> |
---
## 时间函数
```sql
-- 当前时间
current_date() -- 当前日期
current_timestamp() -- 当前时间戳
unix_timestamp() -- 当前 Unix 时间戳(秒)
-- 格式转换
date_format(date_col, 'yyyy-MM-dd') -- 日期格式化
date_format(timestamp_col, 'yyyy-MM-dd HH:mm:ss') -- 时间格式化
to_date(string_col) -- 字符串转日期
to_date(string_col, 'yyyy-MM-dd') -- 字符串转日期(带格式)
from_unixtime(timestamp) -- Unix 时间戳转字符串
from_unixtime(timestamp, 'yyyy-MM-dd') -- 带格式转换
-- 日期计算
date_add(date_col, 7) -- 加7天
date_sub(date_col, 7) -- 减7天
add_months(date_col, 3) -- 加3个月
datediff(end_date, start_date) -- 日期差(天数)
months_between(date1, date2) -- 月份差
-- 日期提取
year(date_col) -- 年
month(date_col) -- 月
day(date_col) -- 日
dayofmonth(date_col) -- 月中第几天
dayofweek(date_col) -- 周几 (1=周日, 7=周六)
hour(timestamp_col) -- 时
minute(timestamp_col) -- 分
second(timestamp_col) -- 秒
quarter(date_col) -- 季度 (1-4)
weekofyear(date_col) -- 年中第几周
last_day(date_col) -- 月末日期
trunc(date_col, 'MM') -- 月初日期
trunc(date_col, 'YY') -- 年初日期
-- Unix 时间戳
unix_timestamp(date_col) -- 转 Unix 时间戳
unix_timestamp(string_col, 'yyyy-MM-dd') -- 指定格式转换
from_unixtime(timestamp) -- Unix 时间戳转字符串
```
---
## 字符串函数
```sql
-- 常用函数
concat(str1, str2, ...) -- 字符串拼接
concat_ws('-', str1, str2, ...) -- 用分隔符拼接
lower(str) -- 转小写
upper(str) -- 转大写
trim(str) -- 去两端空格
ltrim(str) -- 去左空格
rtrim(str) -- 去右空格
length(str) -- 字符串长度
substring(str, pos, len) -- 截取字符串pos从1开始
substr(str, pos, len) -- 同 substring
left(str, len) -- 取左边len个字符
right(str, len) -- 取右边len个字符
reverse(str) -- 反转字符串
repeat(str, n) -- 重复n次
space(n) -- 生成n个空格
-- 查找与替换
instr(str, substr) -- 查找子串位置
locate(substr, str, pos) -- 从pos位置查找
replace(str, old, new) -- 替换
regexp_extract(str, pattern, idx) -- 正则提取
regexp_replace(str, pattern, replacement) -- 正则替换
-- 分割
split(str, delimiter) -- 分割成数组
-- 其他
initcap(str) -- 首字母大写
lpad(str, len, pad) -- 左填充
rpad(str, len, pad) -- 右填充
hex(col) -- 转16进制
unhex(str) -- 16进制转字符串
```
---
## 聚合函数
```sql
-- 基础聚合
COUNT(*) -- 计数含NULL行
COUNT(col) -- 计数不含NULL
COUNT(DISTINCT col) -- 去重计数
SUM(col) -- 求和
AVG(col) -- 平均值
MIN(col) -- 最小值
MAX(col) -- 最大值
-- 集合聚合
collect_list(col) -- 返回数组(不去重)
collect_set(col) -- 返回数组(去重)
-- 统计函数
variance(col) -- 方差
var_pop(col) -- 总体方差
var_samp(col) -- 样本方差
stddev(col) -- 标准差
stddev_pop(col) -- 总体标准差
stddev_samp(col) -- 样本标准差
-- 近似函数
approx_count_distinct(col) -- 近似去重计数(大数据量优化)
-- 其他
first(col) -- 第一个值
last(col) -- 最后一个值
```
---
## 条件表达式
```sql
-- CASE WHEN
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
-- CASE 字段匹配
CASE field
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
-- COALESCE取第一个非空值
COALESCE(col1, col2, default_value)
-- NULLIF相等返回NULL
NULLIF(col1, col2)
-- IF简单条件
IF(condition, true_value, false_value)
-- NVL空值替换
NVL(col, default_value)
```
---
## 复杂类型操作
```sql
-- ARRAY 操作
array(val1, val2, ...) -- 创建数组
array_contains(arr, val) -- 判断是否包含
element_at(arr, idx) -- 取元素idx从1开始
arr[idx] -- 取元素idx从0开始
size(arr) -- 数组长度
array_join(arr, delimiter) -- 数组转字符串
sort_array(arr) -- 排序
array_distinct(arr) -- 去重
-- 展开LATERAL VIEW + explode
-- 展开数组
SELECT id, tag
FROM table
LATERAL VIEW explode(tags) t AS tag;
-- 展开数组带索引
SELECT id, pos, tag
FROM table
LATERAL VIEW posexplode(tags) t AS pos, tag;
-- 展开 Map
SELECT id, map_key, map_value
FROM table
LATERAL VIEW explode(props) m AS map_key, map_value;
-- MAP 操作
map(key1, val1, key2, val2) -- 创建 Map
str_to_map(str, delim1, delim2) -- 字符串转 Map
map_contains(map, key) -- 判断是否包含key
map_keys(map) -- 所有 key返回数组
map_values(map) -- 所有 value返回数组
size(map) -- Map大小
-- STRUCT 操作
named_struct('name1', val1, 'name2', val2) -- 创建结构体
struct_col.field_name -- 访问结构体字段
```
---
## 分区表操作
```sql
-- 创建分区表
CREATE TABLE target_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
)
PARTITIONED BY (day_id STRING)
STORED AS ORC;
-- 静态分区写入
INSERT OVERWRITE TABLE target_table
PARTITION (day_id = '${day_id}')
SELECT id, name, amount FROM source_table;
-- 动态分区写入
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict;
INSERT OVERWRITE TABLE target_table
PARTITION (day_id)
SELECT id, name, amount, day_id FROM source_table;
-- 分区管理
SHOW PARTITIONS target_table;
ALTER TABLE target_table ADD IF NOT EXISTS PARTITION (day_id = '2026-05-10');
ALTER TABLE target_table DROP IF EXISTS PARTITION (day_id = '2026-01-01');
-- MSCK REPAIR恢复分区元数据
MSCK REPAIR TABLE target_table;
```
---
## 与 Spark SQL 的主要差异
| 特性 | Spark SQL | Hive | 说明 |
|------|-----------|------|------|
| **LEFT SEMI JOIN** | ✅ 独立语法 | ✅ 支持(语义相同) | Hive 也可用 IN 子查询替代 |
| **LEFT ANTI JOIN** | ✅ 独立语法 | ✅ 支持(语义相同) | Hive 也可用 NOT IN 替代 |
| **CTE (WITH)** | ✅ 支持 | ✅ Hive 0.13+ 支持 | 都支持但 Hive 中推荐物化临时表 |
| **INSERT OVERWRITE** | ✅ 支持 | ✅ 支持 | 写法一致 |
| **MERGE INTO** | ✅ 支持 | ❌ 不支持 | Hive 不支持 |
| **UPDATE/DELETE** | ❌ 不支持 | 仅 ACID 表支持 | 普通 Hive 表不支持 |
| **collect_list/set** | ✅ 支持 | ✅ 支持 | 完全一致 |
| **LATERAL VIEW** | ✅ 支持 | ✅ 支持Hive 原生) | Hive 首创的语法 |
| **分桶 JOIN** | 可优化 | 可优化SMB JOIN | Hive 分桶优化更成熟 |
| **日期格式** | `yyyy-MM-dd` | `yyyy-MM-dd` | 格式一致 |
| **临时表** | CREATE TEMP TABLE | CREATE TEMPORARY TABLE | 关键字略有不同 |
| **存储格式** | PARQUET/ORC | ORC/PARQUET/TEXTFILE | Hive 支持 TEXTFILE |
| **分区发现** | 自动 | 需 MSCK REPAIR 或 ALTER | Hive 需手动恢复 |
| **复杂类型** | 完整支持 | 完整支持 | 基本一致 |
| **窗口函数** | ✅ 完整支持 | ✅ 完整支持 | 语法一致 |
---
## SQL 生成规则
### 通用规则(所有引擎统一)
1. **禁止使用 CTE (WITH 子句)**,每个主要逻辑步骤必须物化为临时表
2. **先 DROP 再 CREATE**`DROP TABLE IF EXISTS ...; CREATE TABLE ... AS SELECT ...;`
3. **禁止 `SELECT *`**,必须明确列出所有字段
4. 多表查询时所有表必须使用简短别名
5. 每个步骤前添加注释说明
6. **谓词下推**过滤条件前置JOIN 时在 WHERE 中一并添加过滤
7. 临时表命名:`${db_tmp_env}.tmp_{业务简称}_{步骤序号}`
### Hive 特有规则
1. 使用 `INSERT OVERWRITE TABLE ... PARTITION (...)` 写入目标表
2. 动态分区需先 `SET hive.exec.dynamic.partition = true;`
3. 分区列不能出现在表定义的列中Hive 特有约束)
4. 支持 `collect_list` / `collect_set` 聚合
5. 支持 `LATERAL VIEW explode()` 展开数组
6. 日期函数:`date_format()`, `to_date()`, `date_add()`, `add_months()`(和 Spark 一致)
7. 时间范围筛选:
```sql
-- 日账期过滤
WHERE day_id = '${day_id}'
-- 最近N个月月份格式 yyyyMM
WHERE month_id >= date_format(add_months(to_date('${month_id}', 'yyyyMM'), -N), 'yyyyMM')
AND month_id < '${month_id}'
```
### SQL 脚本结构
```sql
-- =====================================================================
-- @SqlName: hive-D-SQL-{表名}
-- @Engine: hive
-- ...(头注释)
-- =====================================================================
-- Step01: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT ...;
-- Step02: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT ...;
-- 最后一步:写入目标表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT ...;
```

View File

@@ -0,0 +1,369 @@
# JOIN 模式速查
## JOIN 类型
| 类型 | 说明 | 结果特点 |
|------|------|----------|
| INNER JOIN | 内连接 | 只返回匹配的行 |
| LEFT JOIN | 左外连接 | 左表全部右表匹配无匹配为NULL |
| RIGHT JOIN | 右外连接 | 右表全部左表匹配无匹配为NULL |
| FULL OUTER JOIN | 全外连接 | 两表全部无匹配处为NULL |
| CROSS JOIN | 交叉连接 | 笛卡尔积(每行与每行组合) |
| LEFT SEMI JOIN | 左半连接 | 左表中在右表有匹配的行(不返回右表列) |
| LEFT ANTI JOIN | 左反连接 | 左表中在右表无匹配的行 |
---
## INNER JOIN
```sql
-- 基本语法
SELECT a.*, b.*
FROM table_a a
INNER JOIN table_b b ON a.id = b.id
-- 等价写法(逗号连接)
SELECT a.*, b.*
FROM table_a a, table_b b
WHERE a.id = b.id
-- 多字段关联
SELECT a.*, b.*
FROM table_a a
INNER JOIN table_b b
ON a.user_id = b.user_id
AND a.order_date = b.order_date
```
**使用场景**:只需要两表都有匹配的数据时使用。
---
## LEFT JOIN
```sql
-- 基本语法
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
-- 处理右表NULL值
SELECT
a.id,
a.name,
COALESCE(b.amount, 0) AS amount, -- NULL转0
IF(b.id IS NULL, '无匹配', '有匹配') AS match_status
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
-- 找出左表中无匹配的行(差集)
SELECT a.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.id
WHERE b.id IS NULL
```
**使用场景**:保留左表所有数据,右表补充信息时使用。
---
## RIGHT JOIN
```sql
-- 基本语法
SELECT a.*, b.*
FROM table_a a
RIGHT JOIN table_b b ON a.id = b.id
-- 等价于 LEFT JOIN 反过来
SELECT a.*, b.*
FROM table_b b
LEFT JOIN table_a a ON b.id = a.id
```
**使用场景**:保留右表所有数据时使用(可改用 LEFT JOIN 反转)。
---
## FULL OUTER JOIN
```sql
-- 基本语法
SELECT
COALESCE(a.id, b.id) AS id, -- 统一ID
a.name,
b.amount
FROM table_a a
FULL OUTER JOIN table_b b ON a.id = b.id
-- 找出两表差异
SELECT
a.id AS a_id,
b.id AS b_id,
CASE
WHEN a.id IS NULL THEN '仅在B表'
WHEN b.id IS NULL THEN '仅在A表'
ELSE '两表都有'
END AS status
FROM table_a a
FULL OUTER JOIN table_b b ON a.id = b.id
WHERE a.id IS NULL OR b.id IS NULL
```
**使用场景**:需要两表完整数据,分析差异时使用。
---
## CROSS JOIN
```sql
-- 基本语法(笛卡尔积)
SELECT a.name, b.color
FROM products a
CROSS JOIN colors b
-- 结果products每行与colors每行组合
-- products有10行colors有5行 → 结果50行
-- 等价写法
SELECT a.name, b.color
FROM products a, colors b -- 无WHERE条件
```
**使用场景**:生成所有组合、测试数据生成时使用。
**注意**:数据量大时慎用,可能产生巨量结果。
---
## LEFT SEMI JOINSpark SQL 特有)
```sql
-- 基本语法
SELECT a.*
FROM table_a a
LEFT SEMI JOIN table_b b ON a.id = b.id
-- 效果返回A表中在B表有匹配的行不返回B表的列
-- 等价于 IN 子查询
SELECT a.*
FROM table_a a
WHERE a.id IN (SELECT id FROM table_b b)
```
**使用场景**:只需要判断左表是否在右表存在,不需要右表数据。
---
## LEFT ANTI JOINSpark SQL 特有)
```sql
-- 基本语法
SELECT a.*
FROM table_a a
LEFT ANTI JOIN table_b b ON a.id = b.id
-- 效果返回A表中在B表无匹配的行
-- 等价于 NOT IN 子查询
SELECT a.*
FROM table_a a
WHERE a.id NOT IN (SELECT id FROM table_b b)
-- 或 NOT EXISTS
SELECT a.*
FROM table_a a
WHERE NOT EXISTS (SELECT 1 FROM table_b b WHERE b.id = a.id)
```
**使用场景**:找出差集(左表中不存在于右表的数据)。
---
## 多表 JOIN
```sql
-- 三表关联
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.id
-- 四表及以上
SELECT
a.col1,
b.col2,
c.col3,
d.col4
FROM table_a a
JOIN table_b b ON a.id = b.a_id
JOIN table_c c ON b.id = c.b_id
JOIN table_d d ON c.id = d.c_id
```
**建议**:多表 JOIN 时,从最小表开始,逐步关联。
---
## 自连接Self Join
```sql
-- 员工与经理关联
SELECT
e.name AS employee,
m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id
-- 查找重复数据
SELECT
a.id,
a.name,
b.id AS duplicate_id
FROM table_a a
JOIN table_a b ON a.name = b.name AND a.id < b.id
-- 紧邻数据比较(前后行)
SELECT
a.date,
a.amount,
b.amount AS prev_amount
FROM sales a
LEFT JOIN sales b ON a.date = b.date + 1
```
---
## JOIN 条件下推优化
**原则**:过滤条件前置,减少 JOIN 数据量。
```sql
-- 推荐:过滤前置
SELECT a.*, b.*
FROM table_a a
JOIN table_b b ON a.id = b.id
WHERE a.date = '${day_id}' -- 先过滤A表
AND b.status = 'active' -- 先过滤B表
-- 不推荐JOIN后再过滤
SELECT a.*, b.*
FROM (
SELECT * FROM table_a -- 未过滤
) a
JOIN (
SELECT * FROM table_b -- 未过滤
) b ON a.id = b.id
WHERE a.date = '${day_id}'
AND b.status = 'active'
```
**性能差异**
- 推荐JOIN 前各表已过滤数据量小JOIN 快
- 不推荐:全表 JOIN 后过滤,数据量大,性能差
---
## JOIN 常见问题
### 问题1关联字段类型不一致
```sql
-- 错误STRING 与 BIGINT 关联
SELECT a.*, b.*
FROM table_a a
JOIN table_b b ON a.id = b.id -- a.id是STRINGb.id是BIGINT
-- 解决:类型转换
SELECT a.*, b.*
FROM table_a a
JOIN table_b b ON CAST(a.id AS BIGINT) = b.id
```
### 问题2关联字段含NULL
```sql
-- 问题NULL关联不上
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b ON a.ref_id = b.id -- a.ref_id有NULL
-- 解决先过滤NULL或用COALESCE
SELECT a.*, b.*
FROM table_a a
LEFT JOIN table_b b ON COALESCE(a.ref_id, 'N/A') = b.id
```
### 问题3多字段关联效率低
```sql
-- 问题:多字段关联
SELECT a.*, b.*
FROM table_a a
JOIN table_b b
ON a.user_id = b.user_id
AND a.order_date = b.order_date
AND a.product_id = b.product_id
-- 解决:合并关联字段
SELECT a.*, b.*
FROM table_a a
JOIN table_b b
ON CONCAT(a.user_id, '_', a.order_date, '_', a.product_id)
= CONCAT(b.user_id, '_', b.order_date, '_', b.product_id)
```
### 问题4大表 JOIN 大表
```sql
-- 问题两表都很大JOIN 慢
SELECT a.*, b.*
FROM large_table_a a
JOIN large_table_b b ON a.id = b.id
-- 解决方案:
-- 1. 尽量前置过滤
-- 2. 使用分区表,按分区关联
-- 3. 调整 Spark 并行度
-- 4. 使用 BROADCAST JOIN其中一表较小
```
---
## BROADCAST JOIN小表广播
```sql
-- Spark SQL 自动判断(需配置阈值)
-- 小表自动广播到所有节点,避免 Shuffle
-- 手动指定广播
SELECT /*+ BROADCAST(b) */ a.*, b.*
FROM large_table_a a
JOIN small_table_b b ON a.id = b.id
-- 多表广播
SELECT /*+ BROADCAST(b), BROADCAST(c) */ a.*, b.*, c.*
FROM large_table_a a
JOIN small_table_b b ON a.id = b.id
JOIN small_table_c c ON a.category = c.category
```
**适用条件**:其中一表数据量较小(通常 < 10MB
---
## JOIN 类型选择指南
| 需求 | 推荐 JOIN | 说明 |
|------|-----------|------|
| 两表都有才保留 | INNER JOIN | 最常用 |
| 左表全部保留 | LEFT JOIN | 补充右表信息 |
| 右表全部保留 | RIGHT JOIN | 或反转用 LEFT JOIN |
| 两表全部保留 | FULL OUTER JOIN | 分析差异 |
| 判断左表是否在右表存在 | LEFT SEMI JOIN | 不需要右表列 |
| 左表不在右表的行 | LEFT ANTI JOIN | 差集查询 |
| 生成所有组合 | CROSS JOIN | 慎用 |

View File

@@ -0,0 +1,336 @@
# Kudu (via Impala) SQL 语法参考
> **重要**Kudu 本身没有 SQL 引擎,通过 Impala 访问。本文档是 Impala SQL 操作 Kudu 表的语法参考。
## 数据类型
| 类型 | 说明 | 示例 |
|------|------|------|
| BOOLEAN | 布尔 | active BOOLEAN |
| TINYINT | 1字节整数 | level TINYINT |
| SMALLINT | 2字节整数 | age SMALLINT |
| INT | 4字节整数 | count INT |
| BIGINT | 8字节整数 | id BIGINT |
| FLOAT | 4字节浮点 | score FLOAT |
| DOUBLE | 8字节浮点 | price DOUBLE |
| DECIMAL(p,s) | 定点数 | amount DECIMAL(18,2) |
| STRING | 变长字符串 | name STRING |
| VARCHAR(n) | 变长字符串(限长) | code VARCHAR(50) |
| CHAR(n) | 定长字符串 | flag CHAR(1) |
| TIMESTAMP | 时间戳(微秒精度) | created_at TIMESTAMP |
| DATE | 日期 | birth_date DATE |
| BINARY | 二进制 | data BINARY |
**注意**Kudu 不支持 ARRAY、MAP、STRUCT 等复杂类型。
---
## 时间函数
```sql
-- 当前时间
NOW() -- 当前日期时间
CURRENT_TIMESTAMP() -- 当前时间戳
UNIX_TIMESTAMP() -- 当前 Unix 时间戳(秒)
TO_DATE(NOW()) -- 当前日期
-- 格式转换
FROM_UNIXTIME(timestamp, 'yyyy-MM-dd') -- Unix 时间戳转格式化字符串
FROM_UNIXTIME(timestamp, 'yyyy-MM-dd HH:mm:ss')
CAST(string_col AS TIMESTAMP) -- 字符串转时间戳
CAST(timestamp_col AS STRING) -- 时间戳转字符串
-- 日期计算
DAYS_ADD(date_col, 7) -- 加7天
DAYS_SUB(date_col, 7) -- 减7天
ADD_MONTHS(date_col, 3) -- 加3个月Impala 6.0+
DATE_ADD(date_col, INTERVAL 7 DAY) -- 加7天标准语法
DATEDIFF(end_date, start_date) -- 日期差(天数)
MONTHS_BETWEEN(date1, date2) -- 月份差
-- 日期提取
YEAR(date_col) -- 年
MONTH(date_col) -- 月
DAY(date_col) -- 日
DAYOFWEEK(date_col) -- 周几 (1=周日)
DAYOFYEAR(date_col) -- 年中第几天
HOUR(timestamp_col) -- 时
MINUTE(timestamp_col) -- 分
SECOND(timestamp_col) -- 秒
QUARTER(date_col) -- 季度 (1-4)
WEEKOFYEAR(date_col) -- 年中第几周
-- Impala 日期格式符
-- yyyy: 4位年, MM: 2位月, dd: 2位日
-- HH: 24小时制, mm: 分钟, ss: 秒
```
---
## 字符串函数
```sql
-- 常用函数
CONCAT(str1, str2) -- 字符串拼接仅2个参数
CONCAT_WS('-', str1, str2, ...) -- 用分隔符拼接
LOWER(str) -- 转小写
UPPER(str) -- 转大写
TRIM(str) -- 去两端空格
LTRIM(str) -- 去左空格
RTRIM(str) -- 去右空格
LENGTH(str) -- 字符串长度
SUBSTR(str, pos, len) -- 截取字符串pos从1开始
SUBSTRING(str, pos, len) -- 同上
LEFT(str, len) -- 取左边len个字符
RIGHT(str, len) -- 取右边len个字符
REVERSE(str) -- 反转字符串
REPEAT(str, n) -- 重复n次
SPACE(n) -- 生成n个空格
-- 查找与替换
INSTR(str, substr) -- 查找子串位置
LOCATE(substr, str, pos) -- 从pos位置查找
REPLACE(str, old, new) -- 替换
REGEXP_EXTRACT(str, pattern, idx) -- 正则提取
REGEXP_REPLACE(str, pattern, replacement) -- 正则替换
-- 分割
SPLIT_PART(str, delimiter, idx) -- 取分割后第idx部分
-- 其他
INITCAP(str) -- 首字母大写
LPAD(str, len, pad) -- 左填充
RPAD(str, len, pad) -- 右填充
HEX(col) -- 转16进制
UNHEX(str) -- 16进制转字符串
```
**注意**Impala 的 `CONCAT` 只接受 2 个参数,多参数拼接用 `CONCAT_WS`
---
## 聚合函数
```sql
-- 基础聚合
COUNT(*) -- 计数含NULL行
COUNT(col) -- 计数不含NULL
COUNT(DISTINCT col) -- 去重计数
SUM(col) -- 求和
AVG(col) -- 平均值
MIN(col) -- 最小值
MAX(col) -- 最大值
-- 集合聚合
GROUP_CONCAT(col SEPARATOR ',') -- 字符串聚合
-- 统计函数
VARIANCE(col) -- 方差
VAR_POP(col) -- 总体方差
VAR_SAMP(col) -- 样本方差
STDDEV(col) -- 标准差
STDDEV_POP(col) -- 总体标准差
STDDEV_SAMP(col) -- 样本标准差
-- 近似函数
NDV(col) -- 近似去重计数Impala 特有,比 COUNT(DISTINCT) 快)
APPROX_COUNT_DISTINCT(col) -- 近似去重计数
-- 其他
FIRST_VALUE(col) -- 窗口内第一个值
LAST_VALUE(col) -- 窗口内最后一个值
```
---
## 条件表达式
```sql
-- CASE WHEN
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
-- CASE 字段匹配
CASE field
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
-- COALESCE取第一个非空值
COALESCE(col1, col2, default_value)
-- NULLIF相等返回NULL
NULLIF(col1, col2)
-- IF简单条件
IF(condition, true_value, false_value)
-- ISNULL / ISNOTNULL
ISNULL(col) -- 判断是否为NULL
ISNOTNULL(col) -- 判断是否不为NULL
-- NVL空值替换
NVL(col, default_value)
```
---
## Kudu 特有操作
### INSERT INTO
```sql
-- 追加写入
INSERT INTO kudu_table VALUES (1, 'test', 100.00);
INSERT INTO kudu_table SELECT * FROM other_table WHERE ...;
```
### UPSERT INTOKudu 核心能力)
```sql
-- 主键存在则更新,不存在则插入
UPSERT INTO kudu_table VALUES (1, 'test', 100.00);
UPSERT INTO kudu_table SELECT * FROM staging_table WHERE ...;
```
### UPDATE
```sql
-- 更新数据(主键列不可更新)
UPDATE kudu_table SET status = 'active' WHERE id = 1;
UPDATE kudu_table SET amount = amount * 1.1 WHERE date < '2026-01-01';
```
### DELETE
```sql
-- 删除数据
DELETE FROM kudu_table WHERE id = 1;
DELETE FROM kudu_table WHERE date < '2026-01-01';
```
### ALTER TABLEKudu 特有)
```sql
-- 添加列
ALTER TABLE kudu_table ADD COLUMNS (new_col STRING COMMENT '新列');
-- 删除列
ALTER TABLE kudu_table DROP COLUMN old_col;
-- 修改列类型
ALTER TABLE kudu_table CHANGE old_name new_name STRING;
-- 添加范围分区
ALTER TABLE kudu_table ADD RANGE PARTITION
'2026-06-01' <= VALUES < '2026-07-01';
-- 删除范围分区
ALTER TABLE kudu_table DROP RANGE PARTITION
'2026-01-01' <= VALUES < '2026-02-01';
```
---
## 分区操作
```sql
-- Hash 分区(建表时指定)
PARTITION BY HASH(id) PARTITIONS 8
-- Range 分区(建表时指定)
PARTITION BY RANGE(stat_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01'
)
-- Hash + Range 组合
PARTITION BY
HASH(id) PARTITIONS 4,
RANGE(stat_date) (...)
```
---
## 与 Spark SQL / Hive 的主要差异
| 特性 | Spark SQL | Hive | Kudu (Impala) | 说明 |
|------|-----------|------|--------------|------|
| **INSERT OVERWRITE** | ✅ | ✅ | ❌ | Kudu 不支持,用 DELETE + INSERT 替代 |
| **UPSERT** | ❌ | ❌ | ✅ | Kudu 独有核心能力 |
| **UPDATE** | ❌ | 仅ACID表 | ✅ | Kudu 原生支持 |
| **DELETE** | ❌ | 仅ACID表 | ✅ | Kudu 原生支持 |
| **主键约束** | ❌ 无约束 | ❌ 无约束 | ✅ 强制主键 | Kudu 表必须有主键 |
| **复杂类型** | ✅ ARRAY/MAP/STRUCT | ✅ 完整支持 | ❌ 不支持 | Kudu 限制 |
| **CONCAT 多参数** | ✅ 任意个数 | ✅ 任意个数 | ❌ 仅2个参数 | Impala 用 CONCAT_WS |
| **近似去重** | `approx_count_distinct` | `approx_count_distinct` | `NDV` | Impala 特有函数名 |
| **临时表链式处理** | ✅ 推荐 | ✅ 推荐 | ❌ 不需要 | Kudu 用 UPSERT 单步 |
| **CTE (WITH)** | ✅ 支持 | ✅ 支持 | ✅ 支持 | 都支持 |
| **MERGE INTO** | ✅ 支持 | ❌ | ❌ | 用 UPSERT 替代 |
| **分区类型** | 目录分区 | 目录分区 | Hash/Range 内置 | Kudu 分区机制不同 |
| **分桶** | 可选 | 可选 | Hash 分区替代 | 概念类似但实现不同 |
| **日期格式** | `yyyy-MM-dd` | `yyyy-MM-dd` | `yyyy-MM-dd` | 格式一致 |
| **collect_list/set** | ✅ | ✅ | ❌ | Impala 用 GROUP_CONCAT |
| **ALTER ADD COLUMN** | ✅ | ✅ | ✅ | Kudu 支持在线加列 |
| **ALTER DROP COLUMN** | 部分支持 | ✅ | ✅ | Kudu 支持在线删列 |
---
## SQL 生成规则
### 通用规则(所有引擎统一)
1. **禁止使用 CTE (WITH 子句)**,每个主要逻辑步骤必须物化为临时表
2. **先 DROP 再 CREATE**`DROP TABLE IF EXISTS ...; CREATE TABLE ... AS SELECT ...;`
3. **禁止 `SELECT *`**,必须明确列出所有字段
4. 多表查询时所有表必须使用简短别名
5. 每个步骤前添加注释说明
6. **谓词下推**过滤条件前置JOIN 时在 WHERE 中一并添加过滤
7. 临时表命名:`${db_tmp_env}.tmp_{业务简称}_{步骤序号}`
### Kudu 特有规则
1. **Kudu 表不支持 `INSERT OVERWRITE`**,用 `DELETE + INSERT``UPSERT` 替代
2. **最后一步优先使用 `UPSERT INTO`**Kudu 核心优势:主键存在则更新,不存在则插入)
3. 需要全量刷新时:先 `DELETE FROM``INSERT INTO`
4. 支持 `UPDATE``DELETE`Kudu 表独有)
5. Kudu 表必须有 `PRIMARY KEY`,主键列不能为 NULL
6. `CONCAT` 只接受 2 个参数,多参数用 `CONCAT_WS`
7. 不支持 `collect_list` / `collect_set`,用 `GROUP_CONCAT` 替代
8. 近似去重用 `NDV()` 函数
9. 时间范围筛选:
```sql
-- 日账期过滤
WHERE stat_date = '${day_id}'
-- 日期范围
WHERE stat_date >= DAYS_SUB(TO_DATE('${day_id}'), 30)
AND stat_date < '${day_id}'
```
### SQL 脚本结构
```sql
-- =====================================================================
-- @SqlName: kudu-D-SQL-{表名}
-- @Engine: kudu
-- ...(头注释)
-- =====================================================================
-- Step01: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT ...;
-- Step02: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT ...;
-- 最后一步UPSERT 写入 Kudu 目标表
UPSERT INTO ${db_eda_env}.target_table
SELECT ...;
```

View File

@@ -0,0 +1,414 @@
# Spark SQL 语法要点
## 数据类型
| 类型 | 说明 | 示例 |
|------|------|------|
| STRING | 字符串 | name STRING |
| INT | 整数 | age INT |
| BIGINT | 大整数 | id BIGINT |
| DOUBLE | 浮点数 | price DOUBLE |
| DECIMAL(p,s) | 定点数 | amount DECIMAL(18,2) |
| BOOLEAN | 布尔 | active BOOLEAN |
| DATE | 日期 | birth_date DATE |
| TIMESTAMP | 时间戳 | created_at TIMESTAMP |
| ARRAY<type> | 数组 | tags ARRAY<STRING> |
| MAP<key,value> | 映射 | props MAP<STRING,STRING> |
| STRUCT<field:type,...> | 结构体 | user STRUCT<id:INT,name:STRING> |
---
## 时间函数
```sql
-- 当前时间
current_date()
current_timestamp()
now()
-- 格式转换
date_format(date_col, 'yyyy-MM-dd')
date_format(timestamp_col, 'yyyy-MM-dd HH:mm:ss')
to_date(string_col, 'yyyy-MM-dd')
to_timestamp(string_col, 'yyyy-MM-dd HH:mm:ss')
-- 日期计算
date_add(date_col, 7) -- 加7天
date_sub(date_col, 7) -- 减7天
add_months(date_col, 3) -- 加3个月
datediff(end_date, start_date) -- 日期差(天数)
-- 日期提取
year(date_col)
month(date_col)
day(date_col)
dayofweek(date_col)
hour(timestamp_col)
minute(timestamp_col)
second(timestamp_col)
-- 季度、周
quarter(date_col) -- 季度 (1-4)
weekofyear(date_col) -- 年中第几周
-- Unix 时间戳
unix_timestamp(date_col) -- 转 Unix 时间戳
from_unixtime(timestamp) -- Unix 时间戳转时间字符串
```
---
## 字符串函数
```sql
-- 常用函数
concat(str1, str2, ...) -- 字符串拼接
concat_ws('-', str1, str2, ...) -- 用分隔符拼接
lower(str) -- 转小写
upper(str) -- 转大写
trim(str) -- 去两端空格
ltrim(str) -- 去左空格
rtrim(str) -- 去右空格
length(str) -- 字符串长度
substring(str, pos, len) -- 截取字符串
left(str, len) -- 取左边len个字符
right(str, len) -- 取右边len个字符
reverse(str) -- 反转字符串
repeat(str, n) -- 重复n次
space(n) -- 生成n个空格
-- 查找与替换
instr(str, substr) -- 查找子串位置
locate(substr, str, pos) -- 从pos位置查找
replace(str, old, new) -- 替换
regexp_extract(str, pattern, idx) -- 正则提取
regexp_replace(str, pattern, replacement) -- 正则替换
-- 分割
split(str, delimiter) -- 分割成数组
split_part(str, delimiter, idx) -- 取分割后的第idx部分
-- 其他
initcap(str) -- 首字母大写
lpad(str, len, pad) -- 左填充
rpad(str, len, pad) -- 右填充
levenshtein(str1, str2) -- 编辑距离
```
---
## 条件表达式
```sql
-- CASE WHEN
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
-- CASE 字段匹配
CASE field
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
-- COALESCE取第一个非空值
COALESCE(col1, col2, default_value)
-- NULLIF相等返回NULL
NULLIF(col1, col2)
-- IF简单条件
IF(condition, true_value, false_value)
-- NVL空值替换
NVL(col, default_value)
```
---
## 聚合函数
```sql
-- 基础聚合
COUNT(*) -- 计数含NULL行
COUNT(col) -- 计数不含NULL
COUNT(DISTINCT col) -- 去重计数
SUM(col) -- 求和
AVG(col) -- 平均值
MIN(col) -- 最小值
MAX(col) -- 最大值
-- 集合聚合
collect_list(col) -- 返回数组(不去重)
collect_set(col) -- 返回数组(去重)
-- 统计函数
variance(col) -- 方差
var_pop(col) -- 总体方差
var_samp(col) -- 样本方差
stddev(col) -- 标准差
stddev_pop(col) -- 总体标准差
stddev_samp(col) -- 样本标准差
-- 近似函数
approx_count_distinct(col) -- 近似去重计数(大数据量优化)
-- 其他
first(col) -- 第一个值
last(col) -- 最后一个值
```
---
## 数学函数
```sql
-- 基础运算
abs(col) -- 绝对值
round(col, digits) -- 四舍五入
ceil(col) -- 向上取整
floor(col) -- 向下取整
sign(col) -- 符号 (-1, 0, 1)
-- 指数与对数
exp(col) -- e的指数
log(col) -- 自然对数
log10(col) -- 10为底对数
log2(col) -- 2为底对数
pow(col, n) -- 幂运算
sqrt(col) -- 平方根
-- 三角函数
sin(col), cos(col), tan(col)
asin(col), acos(col), atan(col)
-- 随机数
rand() -- 随机数 (0-1)
rand(seed) -- 指定种子随机数
-- 其他
cbrt(col) -- 立方根
hex(col) -- 转16进制
unhex(col) -- 16进制转字符串
```
---
## 数组函数
```sql
-- 创建数组
array(val1, val2, ...) -- 创建数组
-- 访问
array_contains(arr, val) -- 判断是否包含
element_at(arr, idx) -- 取元素idx从1开始
arr[idx] -- 取元素idx从0开始
-- 操作
size(arr) -- 数组长度
array_join(arr, delimiter) -- 数组转字符串
concat(arr1, arr2) -- 数组拼接
-- 展开
explode(arr) -- 展开数组为多行
posexplode(arr) -- 展开数组(带位置索引)
-- 排序与去重
sort_array(arr) -- 排序
array_distinct(arr) -- 去重
array_remove(arr, val) -- 移除元素
array_union(arr1, arr2) -- 并集
array_intersect(arr1, arr2) -- 交集
array_except(arr1, arr2) -- 差集
```
---
## Map 函数
```sql
-- 创建 Map
map(key1, val1, key2, val2, ...) -- 创建 Map
str_to_map(str, delim1, delim2) -- 字符串转 Map
-- 访问
map_contains(map, key) -- 判断是否包含key
element_at(map, key) -- 取值
map[key] -- 取值
map_keys(map) -- 取所有key返回数组
map_values(map) -- 取所有value返回数组
-- 操作
size(map) -- Map大小
map_concat(map1, map2) -- Map合并
```
---
## JSON 函数
```sql
-- 解析
get_json_object(json_str, path) -- 提取JSON字段
json_tuple(json_str, field1, ...) -- 提取多个字段
-- 转换
from_json(json_str, schema) -- JSON转结构体
to_json(struct_col) -- 结构体转JSON
-- Schema 定义示例
from_json('{"name":"张三","age":25}', 'name STRING, age INT')
```
---
## 分区表操作
```sql
-- 创建分区表
CREATE TABLE target_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
)
PARTITIONED BY (day_id STRING)
STORED AS PARQUET;
-- 写入指定分区
INSERT OVERWRITE TABLE target_table
PARTITION (day_id = '${day_id}')
SELECT id, name, amount
FROM source_table
WHERE ...
-- 动态分区写入
INSERT OVERWRITE TABLE target_table
PARTITION (day_id)
SELECT id, name, amount, day_id
FROM source_table;
-- 查看分区
SHOW PARTITIONS target_table;
```
---
## 临时表与视图
```sql
-- 创建临时表
CREATE TEMPORARY TABLE tmp_table AS
SELECT ...
-- 创建临时视图
CREATE TEMPORARY VIEW tmp_view AS
SELECT ...
-- 全局临时视图跨Session
CREATE GLOBAL TEMPORARY VIEW global_view AS
SELECT ...
-- 删除
DROP TABLE IF EXISTS tmp_table;
DROP VIEW IF EXISTS tmp_view;
```
---
## MERGE INTO更新插入
```sql
-- MERGE INTO 语法
MERGE INTO target_table t
USING source_table s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET t.name = s.name, t.amount = s.amount
WHEN NOT MATCHED THEN INSERT (id, name, amount) VALUES (s.id, s.name, s.amount)
-- 仅更新
MERGE INTO target_table t
USING source_table s
ON t.id = s.id
WHEN MATCHED THEN UPDATE SET *
-- 仅插入
MERGE INTO target_table t
USING source_table s
ON t.id = s.id
WHEN NOT MATCHED THEN INSERT *
```
---
## Spark SQL 不支持的特性
| PostgreSQL 特性 | Spark SQL | 替代方案 |
|------------------|-----------|----------|
| CREATE INDEX | ❌ 不支持 | 依赖存储格式优化Parquet/ORC |
| CREATE TRIGGER | ❌ 不支持 | 使用程序逻辑处理 |
| FOREIGN KEY 约束 | ❌ 不强制 | 数据关联靠 JOIN 保证 |
| CHECK 约束 | ❌ 不支持 | 使用过滤条件 |
| ON CONFLICT (UPSERT) | 使用 MERGE INTO | - |
| WITH RECURSIVE | Spark 3.x+ 支持 | 或用程序迭代 |
| 物化视图 | ❌ 不支持 | 使用缓存或临时表 |
| 存储过程 | ❌ 不支持 | 使用外部程序 |
| FOR UPDATE 锁 | ❌ 不支持 | 无行级锁概念 |
---
## SQL 生成规则
### 通用规则(所有引擎统一)
1. **禁止使用 CTE (WITH 子句)**,每个主要逻辑步骤必须物化为临时表
2. **先 DROP 再 CREATE**`DROP TABLE IF EXISTS ...; CREATE TABLE ... AS SELECT ...;`
3. **禁止 `SELECT *`**,必须明确列出所有字段
4. 多表查询时所有表必须使用简短别名
5. 每个步骤前添加注释说明
6. **谓词下推**过滤条件前置JOIN 时在 WHERE 中一并添加过滤
7. 临时表命名:`${db_tmp_env}.tmp_{业务简称}_{步骤序号}`
8. 目标表命名:`${db_eda_env}.{目标表名}`
### Spark 特有规则
1. 使用 `INSERT OVERWRITE TABLE` 写入目标表
2. 分区表必须指定分区:`PARTITION (day_id = '${day_id}')`
3. 最后一步写入目标表,中间步骤物化临时表
4. 日期函数:`date_format()`, `to_date()`, `date_add()`, `add_months()`
5. 时间范围筛选:
```sql
-- 日账期过滤
WHERE day_id = '${day_id}'
-- 最近N个月月份格式 yyyyMM
WHERE month_id >= date_format(add_months(to_date('${month_id}', 'yyyyMM'), -N), 'yyyyMM')
AND month_id < '${month_id}'
```
### SQL 脚本结构
```sql
-- =====================================================================
-- @SqlName: spark-D-SQL-{表名}
-- @Engine: spark
-- ...(头注释)
-- =====================================================================
-- Step01: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT ...;
-- Step02: {步骤描述}
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT ...;
-- 最后一步:写入目标表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT ...;
```

View File

@@ -0,0 +1,306 @@
# 窗口函数速查
## 基本语法
```sql
() OVER (
PARTITION BY -- 可选:分组
ORDER BY -- 可选:排序
ROWS/RANGE -- 可选:窗口范围
)
```
---
## 排序函数
| 函数 | 说明 | 特点 | 适用场景 |
|------|------|------|----------|
| ROW_NUMBER() | 连续排名 | 不跳号,相同值不同排名 | 每组取前N条、去重 |
| RANK() | 排名 | 跳号,相同值相同排名 | 成绩排名 |
| DENSE_RANK() | 紧密排名 | 不跳号,相同值相同排名 | 连续名次 |
| NTILE(n) | 分桶 | 分成n组 | 数据分片、抽样 |
### ROW_NUMBER 示例
```sql
-- 每个部门薪资最高的员工(取第一名)
SELECT *
FROM (
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn = 1
-- 去重:每个用户取最新订单
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
FROM orders
) t
WHERE rn = 1
```
### RANK 与 DENSE_RANK 示例
```sql
-- 成绩排名(跳号)
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank, -- 1,2,2,4,5...
DENSE_RANK() OVER (ORDER BY score DESC) AS dense -- 1,2,2,3,4...
FROM students
```
### NTILE 示例
```sql
-- 将用户分成4组业绩分位
SELECT
name,
sales,
NTILE(4) OVER (ORDER BY sales DESC) AS quartile -- 1(最高)到4(最低)
FROM sales_data
```
---
## 聚合函数(窗口内)
| 函数 | 说明 |
|------|------|
| SUM(col) OVER | 累计求和 |
| AVG(col) OVER | 累计平均 |
| COUNT(col) OVER | 窗口内计数 |
| MAX(col) OVER | 窗口内最大值 |
| MIN(col) OVER | 窗口内最小值 |
### 累计求和示例
```sql
-- 累计销售额(从开始到当前)
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS cumulative_amount
FROM daily_sales
-- 分组累计(每个部门累计)
SELECT
date,
department,
amount,
SUM(amount) OVER (PARTITION BY department ORDER BY date) AS dept_cumulative
FROM sales_data
```
### 移动平均示例
```sql
-- 7天移动平均
SELECT
date,
amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales
```
---
## 偏移函数
| 函数 | 说明 | 适用场景 |
|------|------|----------|
| LAG(col, n) | 取前n行的值 | 环比、查看历史值 |
| LAG(col, n, default) | 取前n行无值返回default | 防止NULL |
| LEAD(col, n) | 取后n行的值 | 查看未来值 |
| LEAD(col, n, default) | 取后n行无值返回default | 防止NULL |
| FIRST_VALUE(col) | 窗口第一个值 | 组内首个值 |
| LAST_VALUE(col) | 窗口最后一个值 | 组内末尾值 |
### LAG 示例(环比计算)
```sql
-- 日环比增长
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS prev_day,
amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_growth,
ROUND((amount - LAG(amount, 1) OVER (ORDER BY date))
/ LAG(amount, 1) OVER (ORDER BY date) * 100, 2) AS growth_rate_pct
FROM daily_sales
-- 月度同比(取去年同期)
SELECT
month,
revenue,
LAG(revenue, 12) OVER (ORDER BY month) AS prev_year_revenue,
ROUND((revenue - LAG(revenue, 12) OVER (ORDER BY month))
/ LAG(revenue, 12) OVER (ORDER BY month) * 100, 2) AS yoy_growth_pct
FROM monthly_revenue
```
### LEAD 示例
```sql
-- 查看下一行数据
SELECT
date,
amount,
LEAD(amount, 1) OVER (ORDER BY date) AS next_day_amount
FROM daily_sales
```
### FIRST_VALUE / LAST_VALUE 示例
```sql
-- 每个部门薪资最高和最低的人
SELECT
name,
department,
salary,
FIRST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS highest_paid,
LAST_VALUE(name) OVER (
PARTITION BY department
ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS lowest_paid
FROM employees
```
**注意**LAST_VALUE 默认窗口是 `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`,需要显式指定全窗口才能取到最后值。
---
## 窗口范围定义
### ROWS基于行数
```sql
ROWS BETWEEN 3 PRECEDING AND CURRENT ROW -- 前3行到当前行
ROWS BETWEEN 6 PRECEDING AND 1 FOLLOWING -- 前6行到后1行
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- 从开始到当前
ROWS BETWEEN CURRENT ROW AND 3 FOLLOWING -- 当前行到后3行
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- 全窗口
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING -- 当前行及前后各1行共3行
```
### RANGE基于值范围
```sql
RANGE BETWEEN 100 PRECEDING AND CURRENT ROW -- 值差100以内
RANGE BETWEEN INTERVAL '7' DAY PRECEDING AND CURRENT ROW -- 7天内
```
**ROWS vs RANGE**
- ROWS固定行数不受值影响
- RANGE根据排序字段的值计算范围
---
## 实战案例
### 案例1分组取Top N
```sql
-- 每个部门薪资前3名
SELECT *
FROM (
SELECT
name,
department,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn <= 3
```
### 案例2连续问题判断
```sql
-- 判断是否连续增长连续3天增长
SELECT
date,
amount,
CASE
WHEN amount > LAG(amount, 1) OVER (ORDER BY date)
AND LAG(amount, 1) OVER (ORDER BY date) > LAG(amount, 2) OVER (ORDER BY date)
THEN '连续增长'
ELSE '非连续增长'
END AS trend
FROM daily_sales
```
### 案例3组内占比
```sql
-- 每个部门各员工薪资占比
SELECT
name,
department,
salary,
ROUND(salary / SUM(salary) OVER (PARTITION BY department) * 100, 2) AS salary_pct
FROM employees
```
### 案例4累计百分比帕累托分析
```sql
-- 80/20分析哪些客户贡献了80%销售额
SELECT
customer_id,
sales_amount,
ROUND(SUM(sales_amount) OVER (ORDER BY sales_amount DESC)
/ SUM(sales_amount) OVER () * 100, 2) AS cumulative_pct
FROM customer_sales
ORDER BY sales_amount DESC
```
### 案例5缺失值填充
```sql
-- 用前一个有效值填充NULL
SELECT
date,
amount,
LAST_VALUE(amount IGNORE NULLS) OVER (ORDER BY date) AS filled_amount
FROM sales_data
```
---
## 性能优化建议
1. **减少 PARTITION BY 分组数量**:分组越多,计算越慢
2. **合理使用窗口范围**:避免全窗口扫描
3. **先过滤再窗口**WHERE 条件前置,减少数据量
4. **避免嵌套窗口函数**:多次调用会重复计算
```sql
-- 推荐:先过滤
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn
FROM employees
WHERE hire_date >= '2024-01-01' -- 先过滤,减少数据量
-- 不推荐:先窗口再过滤(窗口函数在全部数据上执行)
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (...) AS rn
FROM employees
) t
WHERE hire_date >= '2024-01-01'
```

View File

@@ -0,0 +1,209 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Apache Doris 建表模板OLAP 多模型)
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 场景1Duplicate Key 模型(明细表)
-- ============================================================================
-- 适用:保留原始明细数据,不做预聚合,数据无冗余
-- 特点:数据按 Key 排序存储,支持所有列的查询和聚合
CREATE TABLE IF NOT EXISTS db_name.detail_table (
-- Key 列(排序字段)
order_id BIGINT COMMENT '订单ID',
order_date DATE COMMENT '订单日期',
user_id BIGINT COMMENT '用户ID',
-- Value 列
user_name VARCHAR(50) COMMENT '用户姓名',
product_id BIGINT COMMENT '商品ID',
product_name VARCHAR(200) COMMENT '商品名称',
quantity INT COMMENT '购买数量',
unit_price DECIMAL(18,2) COMMENT '单价',
total_amount DECIMAL(18,2) COMMENT '总金额',
status VARCHAR(20) COMMENT '订单状态',
create_time DATETIME COMMENT '创建时间'
)
DUPLICATE KEY(order_id, order_date, user_id)
COMMENT '订单明细表'
PARTITION BY RANGE(order_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01'),
PARTITION p202603 VALUES LESS THAN ('2026-04-01')
)
DISTRIBUTED BY HASH(order_id) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'storage_format' = 'V2'
);
-- ============================================================================
-- 场景2Aggregate Key 模型(聚合表)
-- ============================================================================
-- 适用:预聚合场景,相同 Key 的数据自动合并
-- 特点Value 列必须指定聚合函数SUM, REPLACE, MAX, MIN, HLL_UNION, BITMAP_UNION
CREATE TABLE IF NOT EXISTS db_name.agg_table (
-- Key 列(聚合维度)
stat_date DATE COMMENT '统计日期',
department VARCHAR(100) COMMENT '部门名称',
region VARCHAR(100) COMMENT '地区',
-- Value 列(带聚合函数)
order_count BIGINT SUM COMMENT '订单总数',
total_amount DECIMAL(18,2) SUM COMMENT '总金额',
unique_users BIGINT REPLACE COMMENT '去重用户数(预计算值)',
max_amount DECIMAL(18,2) MAX COMMENT '最大金额',
last_update DATETIME REPLACE COMMENT '最后更新时间'
)
AGGREGATE KEY(stat_date, department, region)
COMMENT '部门销售聚合表'
PARTITION BY RANGE(stat_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01')
)
DISTRIBUTED BY HASH(department) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'storage_format' = 'V2'
);
-- ============================================================================
-- 场景3Unique Key 模型(唯一主键表)
-- ============================================================================
-- 适用:需要按主键更新/去重的场景
-- 特点:相同主键的数据保留最新一条(整行替换)
CREATE TABLE IF NOT EXISTS db_name.unique_table (
-- Key 列(主键,必须唯一)
user_id BIGINT COMMENT '用户ID',
-- Value 列
user_name VARCHAR(50) COMMENT '用户姓名',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
vip_level INT COMMENT 'VIP等级',
register_date DATE COMMENT '注册日期',
last_login DATETIME COMMENT '最后登录时间',
status VARCHAR(10) COMMENT '状态'
)
UNIQUE KEY(user_id)
COMMENT '用户信息表(按主键更新)'
DISTRIBUTED BY HASH(user_id) BUCKETS 16
PROPERTIES (
'replication_num' = '3',
'enable_unique_key_merge_based_on_replica' = 'true'
);
-- ============================================================================
-- 场景4带动态分区属性
-- ============================================================================
-- 适用:按日自动创建和管理分区
CREATE TABLE IF NOT EXISTS db_name.auto_partition_table (
stat_date DATE COMMENT '统计日期',
department VARCHAR(100) COMMENT '部门',
metric_value DECIMAL(18,2) SUM COMMENT '指标值',
record_count BIGINT SUM COMMENT '记录数'
)
AGGREGATE KEY(stat_date, department)
COMMENT '自动分区示例表'
PARTITION BY RANGE(stat_date) ()
DISTRIBUTED BY HASH(department) BUCKETS 8
PROPERTIES (
'replication_num' = '3',
'dynamic_partition.enable' = 'true',
'dynamic_partition.time_unit' = 'DAY',
'dynamic_partition.start' = '-30', -- 保留30天历史
'dynamic_partition.end' = '3', -- 预创建3天
'dynamic_partition.prefix' = 'p',
'dynamic_partition.buckets' = '8'
);
-- ============================================================================
-- 场景5多分区 + 多分桶
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.multi_partition_table (
stat_date DATE COMMENT '统计日期',
region VARCHAR(50) COMMENT '地区',
city VARCHAR(50) COMMENT '城市',
user_id BIGINT COMMENT '用户ID',
amount DECIMAL(18,2) SUM COMMENT '金额'
)
AGGREGATE KEY(stat_date, region, city, user_id)
COMMENT '多维度分区示例'
PARTITION BY RANGE(stat_date) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01')
)
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES (
'replication_num' = '3',
'in_memory' = 'false',
'storage_format' = 'V2',
'compression' = 'LZ4'
);
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| BOOLEAN | 布尔 | 状态标志 |
| TINYINT | 1字节整数 | 小范围枚举 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数、大数值 |
| LARGEINT | 16字节整数 | 超大数值 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| DATE | 日期 | 日期字段(无时间) |
| DATETIME | 日期时间 | 时间戳(精确到秒) |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| VARCHAR(n) | 变长字符串 | 名称、描述 |
| STRING | 变长字符串 | 大文本(无长度限制) |
| BITMAP | 位图 | 精确去重(仅聚合模型) |
| HLL | HyperLogLog | 近似去重(仅聚合模型) |
| JSON | JSON | JSON数据存储 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 模型选择
- Duplicate Key保留原始明细不做预聚合
- Aggregate Key预聚合相同 Key 的 Value 自动合并
- Unique Key按主键去重保留最新数据
2. 分区设计
- 按时间字段 RANGE 分区(最常用)
- 支持动态分区自动管理
- 单分区数据量建议 1GB~10GB
3. 分桶设计
- 使用高基数列做 HASH 分桶
- 分桶数 = BE节点数 × CPU核数参考值
- 单桶数据量建议 100MB~1GB
4. 副本数
- 生产环境建议 3 副本
- 测试环境可设 1 副本
5. Key 列选择
- Duplicate Key高频过滤/排序字段
- Aggregate Key聚合维度字段
- Unique Key业务主键
6. 注意事项
- Key 列必须在 Value 列之前
- 分区列必须是 Key 列
- 分桶列必须是 Key 列
- BITMAP/HLL 仅用于 Aggregate 模型的 Value 列
*/

View File

@@ -0,0 +1,128 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-ETL
-- @Version: 2.0
-- @Desc: Apache Doris ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Apache Doris
-- @说明: 统一规范:禁止 CTE每步物化为临时表先 DROP 再 CREATE
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_xxx_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
order_id,
user_id,
dept_id,
total_amount,
status,
order_date
FROM db_name.source_table
WHERE order_date = '${day_id}'
AND status IN ('completed', 'shipped') -- 业务过滤
AND total_amount > 0 -- 数据质量过滤
AND user_id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, dim_department, dim_category
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.order_id,
a.user_id,
a.total_amount,
a.status,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.order_date
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
LEFT JOIN db_name.dim_category c
ON a.category_id = c.category_id;
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
order_date,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT user_id) AS unique_users, -- 去重用户数
SUM(total_amount) AS total_amount, -- 总金额
AVG(total_amount) AS avg_amount, -- 平均金额
MAX(total_amount) AS max_amount -- 最大金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY order_date, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出:目标表
INSERT INTO ${db_eda_env}.target_table
SELECT
-- 业务字段
dept_name,
category_name,
record_count,
unique_users,
total_amount,
avg_amount,
max_amount,
-- 技术字段
NOW() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date -- 统计日期
FROM ${db_tmp_env}.tmp_xxx_03;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:便于调试、断点续跑、统一编码规范
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. Doris 写入方式
- 默认使用 INSERT INTO
- Aggregate Key 表:自动合并相同 Key 的数据
- Unique Key 表:自动按主键去重,保留最新数据
- Doris 2.0+ 也支持 INSERT OVERWRITE
4. 过滤条件前置
- 所有过滤在最早阶段应用
- 减少中间数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. Doris 特有注意事项
- 不支持 LEFT SEMI JOIN / LEFT ANTI JOIN
- 日期函数用 MySQL 风格DATE_FORMAT, DATE_ADD(INTERVAL)
- 不支持 collect_list/collect_set用 GROUP_CONCAT 替代
*/

View File

@@ -0,0 +1,147 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Apache Doris 数据插入模板
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 场景1INSERT INTO追加写入
-- ============================================================================
-- 适用:向 Doris 表追加数据,不会删除已有数据
INSERT INTO db_name.target_table
SELECT
stat_date,
department,
region,
order_count,
total_amount
FROM db_name.source_table
WHERE stat_date = '${day_id}';
-- ============================================================================
-- 场景2INSERT OVERWRITE覆盖写入
-- ============================================================================
-- 适用:覆盖目标表(或指定分区)的全部数据
-- 注意Doris 2.0+ 支持,且仅适用于 Partition 表
-- 覆盖整表
INSERT OVERWRITE db_name.target_table
SELECT
stat_date,
department,
region,
order_count,
total_amount
FROM db_name.source_table;
-- 覆盖指定分区(推荐)
INSERT OVERWRITE db_name.target_table
PARTITION(p202605)
SELECT
department,
region,
order_count,
total_amount
FROM db_name.source_table
WHERE stat_date >= '2026-05-01'
AND stat_date < '2026-06-01';
-- ============================================================================
-- 场景3从查询结果写入ETL 场景)
-- ============================================================================
-- 简单转换后写入
INSERT INTO db_name.target_table
SELECT
order_date,
department,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM db_name.source_orders o
LEFT JOIN db_name.dim_department d ON o.dept_id = d.dept_id
WHERE o.order_date = '${day_id}'
GROUP BY order_date, department;
-- ============================================================================
-- 场景4批量 VALUES 写入
-- ============================================================================
INSERT INTO db_name.target_table (stat_date, department, amount)
VALUES
('2026-05-01', '市场部', 10000.00),
('2026-05-01', '技术部', 25000.00),
('2026-05-01', '运营部', 18000.00);
-- ============================================================================
-- 场景5Stream Load数据导入
-- ============================================================================
-- 适用:大批量数据导入(百万级以上)
-- 注意Stream Load 通过 HTTP 协议提交,不是 SQL 语法
/*
-- curl 命令示例
curl --location-trusted -u user:password \
-H "label:load_order_20260501" \
-H "column_separator:," \
-H "columns:order_id,order_date,user_id,total_amount" \
-T data.csv \
http://fe_host:8030/api/db_name/orders/_stream_load
*/
-- ============================================================================
-- 场景6Broker Load外部数据源导入
-- ============================================================================
/*
LOAD LABEL db_name.load_label_20260501
(
DATA INFILE('hdfs://namenode:8020/path/to/data/*')
INTO TABLE target_table
COLUMNS TERMINATED BY ','
(stat_date, department, region, amount)
SET (amount = amount * 1.0)
)
WITH BROKER 'broker_name'
(
'username' = 'hdfs_user',
'password' = 'hdfs_password'
)
PROPERTIES
(
'timeout' = '3600',
'max_filter_ratio' = '0.01'
);
*/
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT INTO vs INSERT OVERWRITE
- INSERT INTO追加数据不删除已有数据
- INSERT OVERWRITE覆盖数据Doris 2.0+ 支持)
- 日常增量推荐 INSERT INTO全量刷新推荐 INSERT OVERWRITE
2. Doris 不使用临时表链式处理
- 与 Spark 不同Doris 通常用单条 SQL 或 CTE 完成 ETL
- 直接 INSERT INTO ... SELECT ... 即可
3. 字段顺序
- SELECT 字段顺序必须与目标表列定义顺序一致
- 或显式指定列名INSERT INTO table (col1, col2) SELECT ...
4. 数据导入方式选择
- 少量数据INSERT INTO ... SELECT ... 或 INSERT INTO ... VALUES ...
- 大批量导入Stream LoadHTTP PUT最高性能
- HDFS 导入Broker Load
- 外部数据源Routine LoadKafka 等)
5. 性能建议
- 批量写入优于逐条写入
- Stream Load 是最高性能的导入方式
- 建议攒批后一次性写入,避免频繁小批量导入
*/

View File

@@ -0,0 +1,189 @@
-- =====================================================================
-- @Name: DORIS-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Apache Doris 查询模板
-- @TargetDatabase: Apache Doris
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
order_id,
user_name,
total_amount,
create_time
FROM db_name.orders
WHERE order_date = '${day_id}'
AND status = 'completed'
ORDER BY total_amount DESC
LIMIT 100;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOIN
SELECT
o.order_id,
o.total_amount,
u.user_name,
u.vip_level
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.user_id
WHERE o.order_date = '${day_id}'
AND o.status = 'completed';
-- 多表 JOIN
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.user_id
JOIN db_name.order_items oi ON o.order_id = oi.order_id
JOIN db_name.products p ON oi.product_id = p.product_id
WHERE o.order_date BETWEEN '${start_date}' AND '${end_date}';
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
-- GROUP BY + HAVING
SELECT
department,
COUNT(*) AS order_count,
SUM(total_amount) AS total_amount,
AVG(total_amount) AS avg_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_amount DESC;
-- 多字段分组 + 去重计数
SELECT
order_date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(total_amount) AS total_amount
FROM db_name.orders
WHERE order_date BETWEEN '${start_date}' AND '${end_date}'
GROUP BY order_date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
user_name,
total_amount,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY total_amount DESC) AS rn
FROM db_name.orders
WHERE order_date = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
order_date,
daily_amount,
SUM(daily_amount) OVER (ORDER BY order_date) AS cumulative_amount,
AVG(daily_amount) OVER (
ORDER BY order_date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM (
SELECT order_date, SUM(total_amount) AS daily_amount
FROM db_name.orders
GROUP BY order_date
) t;
-- LAG/LEAD环比计算
SELECT
order_date,
daily_amount,
LAG(daily_amount, 1) OVER (ORDER BY order_date) AS prev_amount,
daily_amount - LAG(daily_amount, 1) OVER (ORDER BY order_date) AS daily_change,
ROUND(
(daily_amount - LAG(daily_amount, 1) OVER (ORDER BY order_date))
/ LAG(daily_amount, 1) OVER (ORDER BY order_date) * 100, 2
) AS growth_rate_pct
FROM (
SELECT order_date, SUM(total_amount) AS daily_amount
FROM db_name.orders
GROUP BY order_date
) t;
-- ============================================================================
-- 5. 分页查询
-- ============================================================================
-- LIMIT OFFSET 分页Doris 原生支持)
SELECT
order_id, user_name, total_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
ORDER BY order_id
LIMIT 20 OFFSET 0; -- 第1页每页20条
-- ============================================================================
-- 6. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.orders
WHERE user_id IN (
SELECT user_id FROM db_name.users WHERE vip_level >= 3
)
AND order_date = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.products p
WHERE EXISTS (
SELECT 1 FROM db_name.inventory i
WHERE i.product_id = p.product_id
AND i.quantity > 0
);
-- ============================================================================
-- 7. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
order_date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN total_amount > 1000 THEN total_amount ELSE 0 END) AS high_value_amount
FROM db_name.orders
WHERE order_date = '${day_id}'
GROUP BY order_date;
-- ============================================================================
-- 8. Bitmap 精确去重Doris 特有)
-- ============================================================================
-- 使用 bitmap 做精确去重(需要在 Aggregate Key 模型中定义 BITMAP 类型列)
-- 注意bitmap 函数只能用于包含 BITMAP 类型列的表
-- 精确去重计数(预计算场景,在 Aggregate Key 表中使用 BITMAP_UNION
-- 建表时 Value 列定义user_id_bitmap BITMAP BITMAP_UNION
-- 查询时:
-- SELECT date, bitmap_union_count(user_id_bitmap) AS uv FROM table GROUP BY date;
-- HLL 近似去重
-- 建表时 Value 列定义user_id_hll HLL HLL_UNION
-- 查询时:
-- SELECT date, hll_union_agg(user_id_hll) AS approx_uv FROM table GROUP BY date;

View File

@@ -0,0 +1,211 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Hive 建表模板(内部表/外部表/分区/分桶)
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 场景1内部表Managed Table
-- ============================================================================
-- 适用Hive 管理数据和元数据DROP TABLE 时数据一并删除
CREATE TABLE IF NOT EXISTS db_name.managed_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间',
etl_time TIMESTAMP COMMENT '数据加工时间',
etl_remark STRING COMMENT '备注信息'
)
COMMENT '内部表示例'
STORED AS ORC; -- 推荐存储格式
-- ============================================================================
-- 场景2外部表External Table
-- ============================================================================
-- 适用数据由外部系统管理DROP TABLE 只删元数据不删数据
CREATE EXTERNAL TABLE IF NOT EXISTS db_name.external_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
action STRING COMMENT '操作类型',
page_url STRING COMMENT '页面URL',
ip_address STRING COMMENT 'IP地址',
event_time TIMESTAMP COMMENT '事件时间'
)
COMMENT '日志外部表'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/data/external/logs/';
-- ============================================================================
-- 场景3分区表单分区
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.partitioned_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
order_count BIGINT COMMENT '订单数',
total_amount DECIMAL(18,2) COMMENT '总金额',
department STRING COMMENT '部门',
region STRING COMMENT '地区',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区的统计表'
PARTITIONED BY (day_id STRING COMMENT '统计日期格式yyyy-MM-dd')
STORED AS ORC;
-- ============================================================================
-- 场景4多分区字段
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.multi_partition_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '多分区字段示例表'
PARTITIONED BY (
year_id STRING COMMENT '年份',
month_id STRING COMMENT '月份'
)
STORED AS ORC;
-- ============================================================================
-- 场景5分桶表
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.bucketed_table (
id BIGINT COMMENT '主键ID',
user_id BIGINT COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
amount DECIMAL(18,2) COMMENT '金额'
)
COMMENT '分桶表示例'
PARTITIONED BY (day_id STRING)
CLUSTERED BY (user_id) -- 分桶列
SORTED BY (amount DESC) -- 桶内排序
INTO 16 BUCKETS -- 桶数量
STORED AS ORC;
-- ============================================================================
-- 场景6ORC 格式 + 表属性
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.orc_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT 'ORC格式带属性配置'
PARTITIONED BY (day_id STRING)
STORED AS ORC
TBLPROPERTIES (
'orc.compress' = 'SNAPPY', -- 压缩格式
'orc.create.index' = 'true', -- 创建索引
'transactional' = 'false' -- 非事务表
);
-- ============================================================================
-- 场景7Parquet 格式
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.parquet_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
tags ARRAY<STRING> COMMENT '标签数组',
props MAP<STRING,STRING> COMMENT '属性映射'
)
COMMENT 'Parquet格式表示例'
PARTITIONED BY (day_id STRING)
STORED AS PARQUET
TBLPROPERTIES (
'parquet.compression' = 'SNAPPY'
);
-- ============================================================================
-- 场景8临时表
-- ============================================================================
-- 会话级临时表(会话结束自动删除)
CREATE TEMPORARY TABLE tmp_processing (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
);
-- CTAS 快速创建临时表
CREATE TEMPORARY TABLE tmp_source AS
SELECT id, name, amount
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|-------------------|----------------|------------------------|
| TINYINT | 1字节整数 | 小范围数值 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| BOOLEAN | 布尔 | 状态标志 |
| STRING | 变长字符串 | 名称、描述(最常用) |
| VARCHAR(n) | 变长字符串 | 限定长度字符串 |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| DATE | 日期 | 日期字段 |
| TIMESTAMP | 时间戳 | 时间字段 |
| BINARY | 二进制 | 二进制数据 |
| ARRAY<type> | 数组 | 多值字段 |
| MAP<k,v> | 映射 | 属性字典 |
| STRUCT<f1:t1,...> | 结构体 | 嵌套结构 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 内部表 vs 外部表
- 内部表Hive 管理数据DROP 删数据和元数据
- 外部表外部管理数据DROP 只删元数据
- 生产推荐:原始数据用外部表,加工结果用内部表
2. 存储格式选择
- ORC推荐压缩好列存储支持谓词下推
- PARQUET跨平台兼容好列存储
- TEXTFILE原始数据导入性能最差
3. 分区设计
- 按时间分区最常用day_id, month_id
- 分区列不能出现在表定义的列中Hive 特有)
- 查询时分区列作为普通字段使用
4. 分桶设计
- 选择高基数列做分桶列
- 用于优化 JOIN分桶列相同可做 map-side join
- 用于数据抽样TABLESAMPLE
5. 字段命名规范
- snake_case 格式user_id, total_amount
- 主键id 或 {业务}_id
- 技术字段etl_time, etl_remark
- 分区字段day_id, month_id, year_id
6. COMMENT 必须添加
- 每个字段必须有 COMMENT
- 表必须有 COMMENT
*/

View File

@@ -0,0 +1,138 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-ETL
-- @Version: 1.0
-- @Desc: Hive ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Hive
-- @说明: 和 Spark 类似,禁止 CTE每步物化为临时表
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出tmp_etl_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
id,
name,
amount,
status,
dept_id,
category_id,
created_at,
day_id
FROM db_name.source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status IN ('active', 'valid') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入tmp_xxx_01, dim_department, dim_category
-- 输出tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.id,
a.name,
a.amount,
a.status,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.created_at,
a.day_id
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
AND b.day_id = '${day_id}' -- 维度表分区过滤
LEFT JOIN db_name.dim_category c
ON a.category_id = c.category_id
AND c.day_id = '${day_id}'; -- 维度表分区过滤
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入tmp_xxx_02
-- 输出tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
day_id,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT id) AS unique_count, -- 唯一计数
SUM(amount) AS total_amount, -- 总金额
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount, -- 最大金额
MIN(amount) AS min_amount -- 最小金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY day_id, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入tmp_xxx_03
-- 输出:目标表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段
dept_name,
category_name,
record_count,
unique_count,
total_amount,
avg_amount,
max_amount,
min_amount,
-- 技术字段
current_timestamp() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date; -- 统计日期
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因Hive CTE 可能在某些版本有性能问题
- 物化临时表便于调试和断点续跑
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. 分区过滤必须前置
- 所有源表和维度表查询必须带 day_id 过滤
- 原因:避免全表扫描,提升性能
4. JOIN 条件下推
- 维度表关联时带上分区过滤条件
- 原因:减少关联数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. 目标表写入规范
- 使用 INSERT OVERWRITE覆盖写入幂等
- 明确指定分区
- 补全技术字段etl_time 等)
7. 存储格式建议
- 临时表:默认格式即可(中间结果不需要优化存储)
- 如需优化STORED AS ORC
*/

View File

@@ -0,0 +1,141 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Hive 数据插入模板
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 场景1分区表覆盖写入最常用
-- ============================================================================
-- 适用:每日/每周/每月增量写入分区表
INSERT OVERWRITE TABLE db_name.target_table
PARTITION (day_id = '${day_id}')
SELECT
user_id,
user_name,
order_count,
total_amount,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景2动态分区写入
-- ============================================================================
-- 适用:数据中包含分区值,自动写入对应分区
-- 先启用动态分区
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict;
INSERT OVERWRITE TABLE db_name.target_table
PARTITION (day_id, region) -- 动态分区字段
SELECT
user_id,
user_name,
order_count,
total_amount,
current_timestamp() AS etl_time,
day_id, -- 分区字段1数据中包含
region -- 分区字段2数据中包含
FROM db_name.source_table
WHERE day_id BETWEEN '${start_day}' AND '${end_day}';
-- ============================================================================
-- 场景3追加写入
-- ============================================================================
-- 适用:日志表、流水表(允许追加)
INSERT INTO TABLE db_name.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景4多分区插入Multi-Insert
-- ============================================================================
-- 适用:一次扫描,写入多个目标(提高效率)
FROM db_name.source_table
INSERT OVERWRITE TABLE db_name.target_summary
PARTITION (day_id = '${day_id}')
SELECT
department,
COUNT(*) AS record_count,
SUM(amount) AS total_amount
WHERE day_id = '${day_id}'
GROUP BY department
INSERT OVERWRITE TABLE db_name.target_detail
PARTITION (day_id = '${day_id}')
SELECT
id,
name,
amount,
department
WHERE day_id = '${day_id}'
AND amount > 1000;
-- ============================================================================
-- 场景5导出到文件
-- ============================================================================
INSERT OVERWRITE DIRECTORY '/output/data/export/'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE
SELECT
id,
name,
amount,
day_id
FROM db_name.target_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景6CTASCreate Table As Select
-- ============================================================================
-- 从查询结果创建新表
CREATE TABLE db_name.new_table AS
SELECT
department,
COUNT(*) AS employee_count,
AVG(salary) AS avg_salary
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT OVERWRITE vs INSERT INTO
- INSERT OVERWRITE覆盖分区/表数据(推荐,幂等)
- INSERT INTO追加数据可能产生重复
2. 分区表写入必须指定分区
- 静态分区PARTITION (day_id = '${day_id}')
- 动态分区:需先 SET 配置PARTITION (day_id)
- 混合分区PARTITION (day_id = '2026-05-01', region)
3. 动态分区配置
SET hive.exec.dynamic.partition = true;
SET hive.exec.dynamic.partition.mode = nonstrict; -- 允许全动态
SET hive.exec.max.dynamic.partitions = 1000; -- 最大动态分区数
4. 字段顺序
- SELECT 字段顺序必须与目标表列定义一致
- 分区字段在 SELECT 最后(动态分区时)
5. 性能优化
- 多分区插入Multi-Insert一次扫描多次写入
- INSERT OVERWRITE 比 INSERT INTO 更安全(幂等性)
- 大数据量写入时注意 reducer 数量设置
*/

View File

@@ -0,0 +1,235 @@
-- =====================================================================
-- @Name: HIVE-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Hive 查询模板
-- @TargetDatabase: Hive
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
id,
name,
amount,
created_at
FROM db_name.source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status = 'active'
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOIN
SELECT
a.id,
a.name,
a.amount,
b.category_name
FROM db_name.main_table a
JOIN db_name.dim_table b ON a.category_id = b.id
WHERE a.day_id = '${day_id}';
-- 多表 JOIN带别名
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM db_name.orders o
JOIN db_name.users u ON o.user_id = u.id
JOIN db_name.order_items oi ON o.order_id = oi.order_id
JOIN db_name.products p ON oi.product_id = p.id
WHERE o.day_id = '${day_id}'
AND o.status IN ('completed', 'shipped');
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
-- GROUP BY + HAVING
SELECT
department,
COUNT(*) AS employee_count,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_salary DESC;
-- 多字段分组 + 去重计数
SELECT
date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM db_name.orders
WHERE day_id = '${day_id}'
GROUP BY date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
name,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM db_name.employees
WHERE day_id = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS cumulative_amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales
WHERE day_id = '${day_id}';
-- LAG/LEAD环比
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS prev_amount,
amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_change
FROM daily_sales
WHERE day_id = '${day_id}';
-- ============================================================================
-- 5. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.orders
WHERE user_id IN (
SELECT id FROM db_name.users WHERE vip_level >= 3
)
AND day_id = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.products p
WHERE EXISTS (
SELECT 1 FROM db_name.inventory i
WHERE i.product_id = p.id
AND i.quantity > 0
)
AND p.day_id = '${day_id}';
-- ============================================================================
-- 6. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM db_name.orders
WHERE day_id = '${day_id}'
GROUP BY date;
-- ============================================================================
-- 7. LATERAL VIEW + explodeHive 特有)
-- ============================================================================
-- 展开数组字段
SELECT
id,
name,
tag
FROM db_name.articles
LATERAL VIEW explode(tags) t AS tag
WHERE day_id = '${day_id}';
-- 展开数组并统计
SELECT
tag,
COUNT(*) AS article_count
FROM db_name.articles
LATERAL VIEW explode(tags) t AS tag
WHERE day_id = '${day_id}'
GROUP BY tag;
-- 展开 Map
SELECT
id,
map_key,
map_value
FROM db_name.data_table
LATERAL VIEW explode(props) m AS map_key, map_value
WHERE day_id = '${day_id}';
-- posexplode带索引展开
SELECT
id,
pos,
tag
FROM db_name.articles
LATERAL VIEW posexplode(tags) t AS pos, tag
WHERE day_id = '${day_id}';
-- ============================================================================
-- 8. 复杂类型查询
-- ============================================================================
-- ARRAY 操作
SELECT
id,
size(tags) AS tag_count, -- 数组长度
array_contains(tags, '大数据') AS has_tag, -- 包含判断
tags[0] AS first_tag -- 取第一个元素
FROM db_name.articles
WHERE day_id = '${day_id}';
-- MAP 操作
SELECT
id,
props['city'] AS city, -- 取值
map_keys(props) AS all_keys, -- 所有 key
map_values(props) AS all_values -- 所有 value
FROM db_name.user_table
WHERE day_id = '${day_id}';
-- STRUCT 操作
SELECT
id,
user_info.name AS user_name, -- 结构体字段访问
user_info.age AS user_age
FROM db_name.data_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 9. 集合聚合
-- ============================================================================
-- collect_list / collect_set
SELECT
department,
collect_list(name) AS all_names, -- 收集为数组(不去重)
collect_set(name) AS unique_names, -- 收集为数组(去重)
size(collect_set(name)) AS unique_count
FROM db_name.employees
WHERE day_id = '${day_id}'
GROUP BY department;

View File

@@ -0,0 +1,211 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 建表模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: Kudu 通过 Impala 访问,使用 Impala DDL 操作 Kudu 表
-- =====================================================================
-- ============================================================================
-- 场景1基础表创建Hash 分区)
-- ============================================================================
-- 适用:按主键 Hash 分布数据,写入和点查性能好
CREATE TABLE IF NOT EXISTS db_name.kudu_basic (
-- 主键列Kudu 表必须有主键)
id BIGINT NOT NULL COMMENT '主键ID',
-- 业务字段
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间'
)
PRIMARY KEY (id)
PARTITION BY HASH(id) PARTITIONS 8
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景2Hash + Range 组合分区
-- ============================================================================
-- 适用:按时间范围 + Hash 组合,兼顾范围查询和写入性能
CREATE TABLE IF NOT EXISTS db_name.kudu_range_hash (
-- 主键列(必须包含分区列)
id BIGINT NOT NULL COMMENT '主键ID',
stat_date STRING NOT NULL COMMENT '统计日期 yyyy-MM-dd',
-- 业务字段
department STRING COMMENT '部门',
metric_name STRING COMMENT '指标名称',
metric_value DECIMAL(18,2) COMMENT '指标值',
etl_time TIMESTAMP COMMENT '加工时间'
)
PRIMARY KEY (id, stat_date)
PARTITION BY
HASH(id) PARTITIONS 4,
RANGE(stat_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01',
PARTITION '2026-03-01' <= VALUES < '2026-04-01',
PARTITION '2026-04-01' <= VALUES < '2026-05-01',
PARTITION '2026-05-01' <= VALUES < '2026-06-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3',
'kudu.compression' = 'LZ4'
);
-- ============================================================================
-- 场景3多列主键
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.kudu_composite_pk (
user_id BIGINT NOT NULL COMMENT '用户ID',
order_date STRING NOT NULL COMMENT '订单日期',
order_seq INT NOT NULL COMMENT '当日订单序号',
user_name STRING COMMENT '用户姓名',
product_name STRING COMMENT '商品名称',
quantity INT COMMENT '数量',
total_amount DECIMAL(18,2) COMMENT '总金额',
status STRING COMMENT '状态',
create_time TIMESTAMP COMMENT '创建时间'
)
PRIMARY KEY (user_id, order_date, order_seq)
PARTITION BY
HASH(user_id) PARTITIONS 8,
RANGE(order_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01',
PARTITION '2026-03-01' <= VALUES < '2026-04-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景4纯 Range 分区
-- ============================================================================
-- 适用:按时间顺序写入,范围查询多
CREATE TABLE IF NOT EXISTS db_name.kudu_range_only (
id BIGINT NOT NULL COMMENT '主键ID',
stat_date STRING NOT NULL COMMENT '统计日期',
metric_value DECIMAL(18,2) COMMENT '指标值',
dimension STRING COMMENT '维度',
etl_time TIMESTAMP COMMENT '加工时间'
)
PRIMARY KEY (id, stat_date)
PARTITION BY RANGE(stat_date) (
PARTITION '2026-01-01' <= VALUES < '2026-04-01',
PARTITION '2026-04-01' <= VALUES < '2026-07-01',
PARTITION '2026-07-01' <= VALUES < '2026-10-01',
PARTITION '2026-10-01' <= VALUES < '2027-01-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3'
);
-- ============================================================================
-- 场景5外部表映射已有 Kudu 表
-- ============================================================================
CREATE EXTERNAL TABLE IF NOT EXISTS db_name.kudu_external
STORED AS KUDU
TBLPROPERTIES (
'kudu.master_addresses' = 'kudu-master-1:7051,kudu-master-2:7051,kudu-master-3:7051',
'kudu.table_name' = 'impala.db_name.existing_table'
);
-- ============================================================================
-- 场景6带压缩和副本配置
-- ============================================================================
CREATE TABLE IF NOT EXISTS db_name.kudu_with_props (
id BIGINT NOT NULL COMMENT '主键ID',
data_date STRING NOT NULL COMMENT '数据日期',
content STRING COMMENT '内容',
value DOUBLE COMMENT '数值'
)
PRIMARY KEY (id, data_date)
PARTITION BY
HASH(id) PARTITIONS 8,
RANGE(data_date) (
PARTITION '2026-01-01' <= VALUES < '2026-02-01',
PARTITION '2026-02-01' <= VALUES < '2026-03-01'
)
STORED AS KUDU
TBLPROPERTIES (
'kudu.num_tablet_replicas' = '3',
'kudu.compression' = 'LZ4', -- 压缩算法
'kudu.encryption' = 'false' -- 加密
);
-- ============================================================================
-- 字段类型速查Kudu 支持的类型)
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| BOOLEAN | 布尔 | 状态标志 |
| TINYINT | 1字节整数 | 小范围枚举 |
| SMALLINT | 2字节整数 | 小范围数值 |
| INT | 4字节整数 | 数量、等级 |
| BIGINT | 8字节整数 | ID、计数 |
| FLOAT | 4字节浮点 | 近似计算 |
| DOUBLE | 8字节浮点 | 科学计算 |
| DECIMAL(p,s) | 定点数 | 金额、精确数值 |
| STRING | 变长字符串 | 名称、描述 |
| VARCHAR(n) | 变长字符串 | 限定长度字符串 |
| CHAR(n) | 定长字符串 | 固定长度编码 |
| TIMESTAMP | 时间戳 | 时间字段(微秒精度) |
| DATE | 日期 | 日期字段 |
| BINARY | 二进制 | 二进制数据 |
注意Kudu 不支持 ARRAY, MAP, STRUCT 等复杂类型
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 主键约束Kudu 特有)
- 每张 Kudu 表必须有 PRIMARY KEY
- 主键列不能为 NULL必须 NOT NULL
- 主键值不可 UPDATE只能删除后重新插入
- 主键列必须包含在分区列中
2. 分区策略
- Hash 分区:均匀分布,适合写入和点查
- Range 分区:按范围查询,适合时间序列
- Hash + Range 组合:兼顾两者优势(推荐)
- 分区数 = tablet 数量,影响并行度
3. 分区设计建议
- Hash 分区数:建议 4 的倍数,参考数据量
- Range 分区:按时间维度,定期添加新分区
- 单个 tablet 建议 1GB~10GB
4. 副本数
- 生产环境建议 3 副本(默认)
- Raft 协议保证一致性
5. 压缩
- 推荐 LZ4速度和压缩比平衡
- 可选SNAPPY, ZLIB, LZ4
6. 与 Hive/Spark 表的区别
- Kudu 表支持 UPDATE 和 DELETE
- Kudu 表不支持 INSERT OVERWRITE
- Kudu 表不支持复杂类型ARRAY, MAP, STRUCT
- Kudu 表主键有约束Hive/Spark 无约束
*/

View File

@@ -0,0 +1,146 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-ETL
-- @Version: 2.0
-- @Desc: Kudu (via Impala) ETL 数据处理模板(临时表链式处理)
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: 统一规范:禁止 CTE每步物化为临时表先 DROP 再 CREATE
-- 最后一步用 UPSERT INTO 写入 Kudu 目标表
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_xxx_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
order_id,
user_id,
dept_id,
product_id,
quantity,
amount,
status,
stat_date
FROM db_name.source_table
WHERE stat_date = '${day_id}'
AND status IN ('completed', 'shipped') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND user_id IS NOT NULL; -- NULL过滤
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, dim_department, dim_product
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.order_id,
a.user_id,
a.amount,
a.quantity,
b.dept_name, -- 维度补全:部门名称
c.category_name, -- 维度补全:类别名称
a.stat_date
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN db_name.dim_department b
ON a.dept_id = b.dept_id
LEFT JOIN db_name.dim_product c
ON a.product_id = c.product_id;
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
stat_date,
dept_name,
category_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT user_id) AS unique_users, -- 去重用户数
SUM(amount) AS total_amount, -- 总金额
SUM(quantity) AS total_quantity, -- 总数量
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount -- 最大金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY stat_date, dept_name, category_name;
-- ============================================================================
-- Step04: 最终输出写入 Kudu 目标表
-- ============================================================================
-- 说明:使用 UPSERT 写入 Kudu 目标表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出Kudu 目标表
-- 方式1UPSERT推荐主键存在则更新不存在则插入
UPSERT INTO ${db_eda_env}.target_table
SELECT
-- 主键字段Kudu 表必须有主键)
dept_name,
category_name,
stat_date,
-- 指标字段
record_count,
unique_users,
total_amount,
total_quantity,
avg_amount,
max_amount,
-- 技术字段
NOW() AS etl_time -- 数据加工时间
FROM ${db_tmp_env}.tmp_xxx_03;
-- 方式2需要全量刷新时先删后插
-- DELETE FROM ${db_eda_env}.target_table WHERE stat_date = '${day_id}';
-- INSERT INTO ${db_eda_env}.target_table
-- SELECT ... FROM ${db_tmp_env}.tmp_xxx_03;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:便于调试、断点续跑、统一编码规范
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP TABLE IF EXISTS
- 原因:防止表已存在导致失败
3. Kudu 写入方式
- 推荐 UPSERT INTOKudu 核心优势)
- 主键存在 → 更新(整行替换)
- 主键不存在 → 插入新行
- 需要全量刷新 → 先 DELETE 再 INSERT
4. Kudu 表约束
- 不支持 INSERT OVERWRITE用 UPSERT 或 DELETE + INSERT 替代)
- 必须有 PRIMARY KEY
- 主键列不能为 NULL
- 支持 UPDATE 和 DELETE
5. 过滤条件前置
- 所有过滤在最早阶段应用
- 减少中间数据量
6. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
7. Kudu 特有注意事项
- CONCAT 只接受 2 个参数,多参数用 CONCAT_WS
- 不支持 collect_list/collect_set用 GROUP_CONCAT 替代
- 近似去重用 NDV() 函数
*/

View File

@@ -0,0 +1,160 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 数据插入模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- @说明: Kudu 表不支持 INSERT OVERWRITE支持 INSERT INTO 和 UPSERT
-- =====================================================================
-- ============================================================================
-- 场景1INSERT INTO追加写入
-- ============================================================================
-- 适用:向 Kudu 表追加新数据
INSERT INTO db_name.kudu_table
SELECT
id,
stat_date,
name,
department,
amount,
current_timestamp() AS etl_time
FROM db_name.source_table
WHERE stat_date = '${day_id}';
-- ============================================================================
-- 场景2UPSERT INTO更新插入Kudu 特有)
-- ============================================================================
-- 适用:如果主键存在则更新,不存在则插入
-- 这是 Kudu 的核心优势,其他 Hive/Spark 表不支持
-- 基础 UPSERT
UPSERT INTO db_name.kudu_table
SELECT
id,
stat_date,
name,
department,
amount,
current_timestamp() AS etl_time
FROM db_name.staging_table
WHERE stat_date = '${day_id}';
-- 聚合后 UPSERT增量更新指标表
UPSERT INTO db_name.kudu_metrics
SELECT
department,
'${day_id}' AS stat_date,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
current_timestamp() AS etl_time
FROM db_name.incremental_orders
WHERE stat_date = '${day_id}'
GROUP BY department;
-- ============================================================================
-- 场景3UPDATEKudu 表特有)
-- ============================================================================
-- 适用:修改已有数据
-- 注意:主键列不能被 UPDATE
-- 单条更新
UPDATE db_name.kudu_table
SET status = 'processed',
updated_at = current_timestamp()
WHERE id = 12345;
-- 批量条件更新
UPDATE db_name.kudu_table
SET status = 'expired',
updated_at = current_timestamp()
WHERE stat_date < '2026-01-01'
AND status = 'active';
-- 关联更新(用子查询)
UPDATE db_name.kudu_table t
SET t.department = d.new_dept_name
FROM db_name.dept_mapping d
WHERE t.department = d.old_dept_name;
-- ============================================================================
-- 场景4DELETEKudu 表特有)
-- ============================================================================
-- 适用:删除数据
-- 注意Kudu 的 DELETE 比 Hive/Spark 方便得多
-- 条件删除
DELETE FROM db_name.kudu_table
WHERE stat_date < '2026-01-01';
-- 按主键删除
DELETE FROM db_name.kudu_table
WHERE id IN (1001, 1002, 1003);
-- 关联删除(用子查询)
DELETE FROM db_name.kudu_table
WHERE user_id IN (
SELECT user_id FROM db_name.blacklist
);
-- ============================================================================
-- 场景5从查询结果写入
-- ============================================================================
-- 简单 ETL清洗后写入
INSERT INTO db_name.kudu_target
SELECT
id,
'${day_id}' AS stat_date,
name,
COALESCE(department, '未知') AS department,
amount,
current_timestamp() AS etl_time
FROM db_name.raw_data
WHERE stat_date = '${day_id}'
AND id IS NOT NULL
AND amount > 0;
-- ============================================================================
-- 场景6批量 VALUES 写入
-- ============================================================================
INSERT INTO db_name.kudu_table (id, stat_date, name, amount)
VALUES
(1, '2026-05-01', '测试1', 100.00),
(2, '2026-05-01', '测试2', 200.00),
(3, '2026-05-01', '测试3', 300.00);
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. Kudu 表与 Hive/Spark 表的核心区别
- 支持 INSERT INTO
- 支持 INSERT OVERWRITE不支持
- 支持 UPSERTKudu 独有,核心能力)
- 支持 UPDATEKudu 独有)
- 支持 DELETEKudu 独有)
2. UPSERT 是 Kudu 的核心优势
- 主键存在 → 更新(整行替换)
- 主键不存在 → 插入新行
- 适用于:增量更新、数据修正、指标回填
3. INSERT INTO 注意事项
- 如果主键冲突会报错(不会自动去重)
- 需要确保写入数据的主键不重复,或使用 UPSERT
4. UPDATE 限制
- 主键列不能被 UPDATE
- WHERE 条件建议包含主键或分区列(性能)
5. DELETE 建议
- 删除大量数据时按分区范围删除
- 定期清理历史数据
6. 性能建议
- 批量写入优于逐条写入
- UPSERT 比 DELETE + INSERT 更高效
- 利用主键做点查,避免全表扫描
*/

View File

@@ -0,0 +1,179 @@
-- =====================================================================
-- @Name: KUDU-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: Kudu (via Impala) 查询模板
-- @TargetDatabase: Apache Kudu (via Impala)
-- =====================================================================
-- ============================================================================
-- 1. 单表查询
-- ============================================================================
SELECT
id,
name,
amount,
created_at
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
AND status = 'active'
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- 2. JOIN 查询
-- ============================================================================
-- 两表 JOINKudu 表 JOIN 非 Kudu 表也支持)
SELECT
k.id,
k.name,
k.amount,
d.dept_name
FROM db_name.kudu_table k
JOIN db_name.dim_department d ON k.dept_id = d.dept_id
WHERE k.stat_date = '${day_id}';
-- 多表 JOIN
SELECT
k.id,
k.user_name,
p.product_name,
k.quantity,
k.total_amount
FROM db_name.kudu_orders k
JOIN db_name.dim_users u ON k.user_id = u.user_id
JOIN db_name.dim_products p ON k.product_id = p.product_id
WHERE k.stat_date BETWEEN '${start_date}' AND '${end_date}';
-- ============================================================================
-- 3. 聚合查询
-- ============================================================================
SELECT
department,
COUNT(*) AS record_count,
SUM(amount) AS total_amount,
AVG(amount) AS avg_amount,
MAX(amount) AS max_amount,
MIN(amount) AS min_amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_amount DESC;
-- 多字段分组 + 去重计数
SELECT
stat_date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM db_name.kudu_table
WHERE stat_date BETWEEN '${start_date}' AND '${end_date}'
GROUP BY stat_date, region;
-- ============================================================================
-- 4. 窗口函数
-- ============================================================================
-- ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
user_name,
amount,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY amount DESC) AS rn
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
) t
WHERE rn <= 3;
-- 累计聚合
SELECT
stat_date,
daily_amount,
SUM(daily_amount) OVER (ORDER BY stat_date) AS cumulative_amount
FROM (
SELECT stat_date, SUM(amount) AS daily_amount
FROM db_name.kudu_table
GROUP BY stat_date
) t;
-- LAG/LEAD环比
SELECT
stat_date,
daily_amount,
LAG(daily_amount, 1) OVER (ORDER BY stat_date) AS prev_amount,
daily_amount - LAG(daily_amount, 1) OVER (ORDER BY stat_date) AS daily_change
FROM (
SELECT stat_date, SUM(amount) AS daily_amount
FROM db_name.kudu_table
GROUP BY stat_date
) t;
-- ============================================================================
-- 5. 子查询
-- ============================================================================
-- IN 子查询
SELECT *
FROM db_name.kudu_table
WHERE user_id IN (
SELECT user_id FROM db_name.vip_users WHERE vip_level >= 3
)
AND stat_date = '${day_id}';
-- EXISTS 子查询
SELECT *
FROM db_name.kudu_products p
WHERE EXISTS (
SELECT 1 FROM db_name.kudu_inventory i
WHERE i.product_id = p.product_id
AND i.quantity > 0
);
-- ============================================================================
-- 6. 条件聚合CASE WHEN + 聚合)
-- ============================================================================
SELECT
stat_date,
COUNT(*) AS total_count,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
GROUP BY stat_date;
-- ============================================================================
-- 7. LIMIT / OFFSET分页
-- ============================================================================
SELECT id, name, amount
FROM db_name.kudu_table
WHERE stat_date = '${day_id}'
ORDER BY id
LIMIT 20 OFFSET 0;
-- ============================================================================
-- 8. Kudu 特有:通过主键高效点查
-- ============================================================================
-- Kudu 主键查询可跳过扫描,直接定位 tablet
-- 单主键点查
SELECT * FROM db_name.kudu_table
WHERE id = 12345;
-- 复合主键点查
SELECT * FROM db_name.kudu_composite_pk
WHERE user_id = 1001
AND order_date = '2026-05-01'
AND order_seq = 1;
-- 主键 IN 查询
SELECT * FROM db_name.kudu_table
WHERE id IN (1001, 1002, 1003, 1004, 1005);

View File

@@ -0,0 +1,176 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-CREATE
-- @Version: 1.0
-- @Desc: 建表模板CREATE TABLE
-- @TargetTables: {新表名}
-- @TargetDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 场景1基础表创建非分区
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.basic_table (
-- 主键/标识字段
id BIGINT COMMENT '主键ID',
-- 业务字段
name STRING COMMENT '名称',
category STRING COMMENT '类别',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
-- 时间字段
created_at TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP COMMENT '更新时间',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间',
etl_remark STRING COMMENT '备注信息'
)
COMMENT '基础业务表'
STORED AS PARQUET; -- 存储格式
-- ============================================================================
-- 场景2分区表创建单分区字段
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.partitioned_table (
-- 主键/标识字段
id BIGINT COMMENT '主键ID',
-- 业务字段
user_id STRING COMMENT '用户ID',
user_name STRING COMMENT '用户姓名',
order_count BIGINT COMMENT '订单数',
total_amount DECIMAL(18,2) COMMENT '总金额',
-- 维度字段
department STRING COMMENT '部门',
region STRING COMMENT '地区',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区的统计表'
PARTITIONED BY (day_id STRING COMMENT '统计日期格式yyyy-MM-dd')
STORED AS PARQUET;
-- ============================================================================
-- 场景3多分区字段表
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.multi_partition_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '多分区字段示例表'
PARTITIONED BY (
year_id STRING COMMENT '年份',
month_id STRING COMMENT '月份'
)
STORED AS PARQUET;
-- ============================================================================
-- 场景4带表属性配置
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.configured_table (
id BIGINT COMMENT '主键ID',
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '带属性配置的表'
PARTITIONED BY (day_id STRING)
STORED AS PARQUET
TBLPROPERTIES (
'parquet.compression' = 'SNAPPY', -- 压缩格式
'spark.sql.partitionOverwriteMode' = 'dynamic' -- 动态分区覆盖模式
);
-- ============================================================================
-- 场景5Paimon 表创建(主键表)
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.paimon_pk_table (
-- 主键字段Paimon 主键表必须包含所有主键字段)
id BIGINT COMMENT '主键ID',
day_id STRING COMMENT '分区日期',
-- 业务字段
name STRING COMMENT '名称',
amount DECIMAL(18,2) COMMENT '金额',
status STRING COMMENT '状态',
-- 技术字段
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT 'Paimon 主键表(支持 MERGE INTO'
PARTITIONED BY (day_id)
TBLPROPERTIES (
'primary-key' = 'id,day_id', -- 主键定义
'bucket' = '4', -- 分桶数
'changelog-producer' = 'input' -- 变更日志生产
);
-- ============================================================================
-- 场景6临时表创建
-- ============================================================================
CREATE TEMPORARY TABLE tmp_processing_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2)
);
-- 或使用 AS 创建临时表
CREATE TEMPORARY TABLE tmp_source AS
SELECT id, name, amount
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 字段类型速查
-- ============================================================================
/*
| 类型 | 说明 | 适用场景 |
|---------------|----------------|------------------------|
| STRING | 字符串 | 名称、编码、描述 |
| INT | 整数 | 数量、等级、标志 |
| BIGINT | 大整数 | ID、计数、金额整数 |
| DECIMAL(p,s) | 定点数 | 金额、比例、精度数值 |
| DOUBLE | 浮点数 | 科学计算(慎用于金额) |
| BOOLEAN | 布尔 | 状态标志 |
| DATE | 日期 | 日期字段 |
| TIMESTAMP | 时间戳 | 时间字段 |
| ARRAY<type> | 数组 | 多值字段 |
| MAP<k,v> | 映射 | 属性字典 |
*/
-- ============================================================================
-- 建表规范说明
-- ============================================================================
/*
1. 字段命名规范
- 使用 snake_caseuser_id, total_amount
- 主键id 或 {业务}_id
- 技术字段etl_time, etl_remark
2. COMMENT 必须添加
- 每个字段必须有 COMMENT
- 表必须有 COMMENT
3. 分区字段选择
- 按时间分区day_id, month_id
- 分区粒度:日分区最常用
4. 存储格式
- 推荐PARQUET列存储压缩好
- 可选ORC、AVRO
5. Paimon 表特性
- primary-key主键字段列表
- bucket分桶数影响并发
- 支持 MERGE INTO 操作
6. 表属性配置
- 压缩格式SNAPPY推荐、GZIP、LZ4
- 动态分区模式dynamic推荐
*/

View File

@@ -0,0 +1,148 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-ETL
-- @Version: 1.0
-- @Desc: ETL 数据处理模板(临时表链式处理)
-- @TargetTables: ${db_eda_env}.{目标表名}
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- @任务调度频度: {日/周/月}
-- @修改记录:
-- 版本号 更新时间 更新人员 更新内容
-- V1.0 {日期} {人员} 创建脚本
-- @数据处理步骤:
-- Step01: {步骤描述}
-- Step02: {步骤描述}
-- Step03: {步骤描述}
-- 参数说明
-- 账期参数:
-- ${day_id} 日账期格式20250101
-- 环境变量:
-- 变量名 测试环境值 生产环境值
-- ${db_tmp_env} {库名} {库名}
-- ${db_eda_env} {库名} {库名}
-- =====================================================================
-- ============================================================================
-- Step01: 基础清洗与过滤
-- ============================================================================
-- 说明:从源表读取数据,进行基础过滤和清洗
-- 输入:{源表名}
-- 输出:${db_tmp_env}.tmp_{表名}_01
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_01;
CREATE TABLE ${db_tmp_env}.tmp_xxx_01 AS
SELECT
id,
name,
amount,
status,
created_at,
day_id
FROM source_table
WHERE day_id = '${day_id}' -- 分区过滤(必须)
AND status IN ('active', 'valid') -- 业务过滤
AND amount > 0 -- 数据质量过滤
AND id IS NOT NULL -- NULL过滤;
-- ============================================================================
-- Step02: 多表关联与维度补全
-- ============================================================================
-- 说明:关联维度表,补全业务属性字段
-- 输入:${db_tmp_env}.tmp_xxx_01, {维度表1}, {维度表2}
-- 输出:${db_tmp_env}.tmp_xxx_02
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_02;
CREATE TABLE ${db_tmp_env}.tmp_xxx_02 AS
SELECT
a.id,
a.name,
a.amount,
a.status,
b.category_name, -- 维度补全:类别名称
c.department_name, -- 维度补全:部门名称
a.created_at,
a.day_id
FROM ${db_tmp_env}.tmp_xxx_01 a
LEFT JOIN dim_category b
ON a.category_id = b.id
AND b.day_id = '${day_id}' -- 维度表分区过滤
LEFT JOIN dim_department c
ON a.department_id = c.id
AND c.day_id = '${day_id}'; -- 维度表分区过滤
-- ============================================================================
-- Step03: 聚合计算与指标生成
-- ============================================================================
-- 说明:按业务维度聚合,计算统计指标
-- 输入:${db_tmp_env}.tmp_xxx_02
-- 输出:${db_tmp_env}.tmp_xxx_03
DROP TABLE IF EXISTS ${db_tmp_env}.tmp_xxx_03;
CREATE TABLE ${db_tmp_env}.tmp_xxx_03 AS
SELECT
day_id,
category_name,
department_name,
COUNT(*) AS record_count, -- 记录数
COUNT(DISTINCT id) AS unique_count, -- 唯一计数
SUM(amount) AS total_amount, -- 总金额
AVG(amount) AS avg_amount, -- 平均金额
MAX(amount) AS max_amount, -- 最大金额
MIN(amount) AS min_amount -- 最小金额
FROM ${db_tmp_env}.tmp_xxx_02
GROUP BY day_id, category_name, department_name;
-- ============================================================================
-- Step04: 最终输出写入目标表
-- ============================================================================
-- 说明:补全目标表标准字段,写入结果表
-- 输入:${db_tmp_env}.tmp_xxx_03
-- 输出:${db_eda_env}.{目标表名}
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段
category_name,
department_name,
record_count,
unique_count,
total_amount,
avg_amount,
max_amount,
min_amount,
-- 技术字段
current_timestamp() AS etl_time, -- 数据加工时间
'${day_id}' AS stat_date -- 统计日期;
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. 禁止使用 CTE (WITH 子句)
- 每个步骤必须物化为临时表
- 原因:避免内存溢出,便于调试和断点续跑
2. 先 DROP 再 CREATE
- 每个临时表创建前必须先 DROP
- 原因:防止表已存在导致失败
3. 分区过滤必须前置
- 所有源表和维度表查询必须带 day_id 过滤
- 原因:避免全表扫描,提升性能
4. JOIN 条件下推
- 维度表关联时带上分区过滤条件
- 原因:减少关联数据量
5. 临时表命名规范
- 格式tmp_{业务简称}_{步骤序号}
- 示例tmp_order_stats_01, tmp_order_stats_02
6. 目标表写入规范
- 使用 INSERT OVERWRITE覆盖写入
- 明确指定分区
- 补全技术字段etl_time 等)
*/

View File

@@ -0,0 +1,131 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-INSERT
-- @Version: 1.0
-- @Desc: 数据插入模板INSERT OVERWRITE
-- @TargetTables: ${db_eda_env}.{目标表名}
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 场景1分区表覆盖写入
-- ============================================================================
-- 适用:每日/每周/每月增量写入分区表
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景2动态分区写入
-- ============================================================================
-- 适用:多分区字段,数据中包含分区值
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id, region) -- 动态分区字段
SELECT
field1,
field2,
field3,
day_id, -- 分区字段1数据中包含
region, -- 分区字段2数据中包含
current_timestamp() AS etl_time
FROM source_table
WHERE day_id BETWEEN '${start_day}' AND '${end_day}';
-- ============================================================================
-- 场景3全表覆盖写入
-- ============================================================================
-- 适用:全量刷新、初始化数据
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table;
-- ============================================================================
-- 场景4追加写入慎用
-- ============================================================================
-- 适用:日志表、流水表(无分区或允许重复)
INSERT INTO TABLE ${db_eda_env}.target_table
SELECT
field1,
field2,
field3,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- ============================================================================
-- 场景5从临时表写入目标表
-- ============================================================================
-- 适用ETL 流程最后一步
INSERT OVERWRITE TABLE ${db_eda_env}.target_table
PARTITION (day_id = '${day_id}')
SELECT
-- 业务字段(与目标表字段顺序一致)
user_id,
user_name,
order_count,
total_amount,
-- 技术字段
current_timestamp() AS etl_time,
'${day_id}' AS stat_date
FROM ${db_tmp_env}.tmp_xxx_final;
-- ============================================================================
-- 场景6MERGE INTO更新插入
-- ============================================================================
-- 适用:增量更新、修正历史数据
MERGE INTO ${db_eda_env}.target_table t
USING ${db_tmp_env}.tmp_xxx_source s
ON t.id = s.id AND t.day_id = s.day_id
WHEN MATCHED THEN
UPDATE SET
t.name = s.name,
t.amount = s.amount,
t.etl_time = current_timestamp()
WHEN NOT MATCHED THEN
INSERT (id, day_id, name, amount, etl_time)
VALUES (s.id, s.day_id, s.name, s.amount, current_timestamp());
-- ============================================================================
-- 关键规则说明
-- ============================================================================
/*
1. INSERT OVERWRITE vs INSERT INTO
- INSERT OVERWRITE覆盖写入推荐
- INSERT INTO追加写入可能导致重复数据
2. 分区表写入必须指定分区
- 避免全表覆盖导致历史数据丢失
- 格式PARTITION (day_id = '${day_id}')
3. 字段顺序必须与目标表一致
- 目标表字段顺序:业务字段 → 技术字段 → 分区字段
- SELECT 字段顺序必须匹配
4. 技术字段补全
- etl_time数据写入时间
- stat_date统计日期可选
- etl_remark备注信息可选
5. MERGE INTO 注意事项
- Spark 3.x+ 支持
- 目标表必须支持事务(如 Paimon/Delta
- 关联字段必须唯一(避免多条匹配)
*/

View File

@@ -0,0 +1,179 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-PARTITION
-- @Version: 1.0
-- @Desc: 分区表操作模板
-- @TargetTables: {分区表名}
-- @TargetDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 分区表创建
-- ============================================================================
CREATE TABLE IF NOT EXISTS ${db_eda_env}.daily_partition_table (
id BIGINT COMMENT '主键ID',
user_id STRING COMMENT '用户ID',
amount DECIMAL(18,2) COMMENT '金额',
etl_time TIMESTAMP COMMENT '数据加工时间'
)
COMMENT '按日分区表'
PARTITIONED BY (day_id STRING COMMENT '统计日期')
STORED AS PARQUET;
-- ============================================================================
-- 分区写入操作
-- ============================================================================
-- 1. 静态分区写入(指定分区值)
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id = '2026-05-09')
SELECT
id,
user_id,
amount,
current_timestamp() AS etl_time
FROM source_table
WHERE day_id = '${day_id}';
-- 2. 动态分区写入(数据中包含分区值)
-- 需要先设置动态分区模式
SET spark.sql.partitionOverwriteMode = dynamic;
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id) -- 动态分区字段
SELECT
id,
user_id,
amount,
current_timestamp() AS etl_time,
day_id -- 数据中包含分区值
FROM source_table
WHERE day_id BETWEEN '2026-05-01' AND '2026-05-09';
-- ============================================================================
-- 分区查询操作
-- ============================================================================
-- 3. 单分区查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id = '2026-05-09';
-- 4. 多分区查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id IN ('2026-05-01', '2026-05-02', '2026-05-03');
-- 5. 分区范围查询
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id >= '2026-05-01'
AND day_id <= '2026-05-09';
-- 6. 最近 N 天分区查询(动态计算)
SELECT *
FROM ${db_eda_env}.daily_partition_table
WHERE day_id >= date_format(date_sub(current_date(), 30), 'yyyy-MM-dd');
-- ============================================================================
-- 分区管理操作
-- ============================================================================
-- 7. 查看分区列表
SHOW PARTITIONS ${db_eda_env}.daily_partition_table;
-- 8. 查看特定分区详情
DESCRIBE EXTENDED ${db_eda_env}.daily_partition_table PARTITION (day_id = '2026-05-09');
-- 9. 添加分区(手动创建空分区,部分表类型支持)
ALTER TABLE ${db_eda_env}.daily_partition_table
ADD IF NOT EXISTS PARTITION (day_id = '2026-05-10');
-- 10. 删除分区(清理历史数据)
ALTER TABLE ${db_eda_env}.daily_partition_table
DROP IF EXISTS PARTITION (day_id = '2026-01-01');
-- ============================================================================
-- 多分区字段操作
-- ============================================================================
-- 11. 多分区字段表创建
CREATE TABLE IF NOT EXISTS ${db_eda_env}.multi_partition_table (
id BIGINT,
name STRING,
amount DECIMAL(18,2),
etl_time TIMESTAMP
)
PARTITIONED BY (year_id STRING, month_id STRING)
STORED AS PARQUET;
-- 12. 多分区字段写入
INSERT OVERWRITE TABLE ${db_eda_env}.multi_partition_table
PARTITION (year_id = '2026', month_id = '05')
SELECT
id,
name,
amount,
current_timestamp() AS etl_time
FROM source_table
WHERE year_id = '2026' AND month_id = '05';
-- 13. 多分区字段动态写入
SET spark.sql.partitionOverwriteMode = dynamic;
INSERT OVERWRITE TABLE ${db_eda_env}.multi_partition_table
PARTITION (year_id, month_id)
SELECT
id,
name,
amount,
current_timestamp() AS etl_time,
year_id,
month_id
FROM source_table;
-- ============================================================================
-- 分区数据清理
-- ============================================================================
-- 14. 清理指定分区数据
INSERT OVERWRITE TABLE ${db_eda_env}.daily_partition_table
PARTITION (day_id = '2026-05-09')
SELECT * FROM ${db_eda_env}.daily_partition_table
WHERE day_id = '2026-05-09'
AND status = 'valid'; -- 只保留有效数据
-- 15. 清理 N 天前分区(批量)
-- 使用脚本或程序循环执行
-- ALTER TABLE xxx DROP PARTITION (day_id = '历史分区')
-- ============================================================================
-- 分区最佳实践
-- ============================================================================
/*
1. 分区字段选择原则
- 查询高频过滤字段
- 数据量分布均匀的字段
- 时间字段最常用day_id, month_id
2. 分区粒度选择
- 日增量数据 → day_id 分区
- 月增量数据 → month_id 分区
- 大数据量 → 可细分到 hour_id
3. 分区数量控制
- 单表分区数建议 < 10000
- 过多分区影响元数据性能
4. 查询必须带分区过滤
- 避免SELECT * FROM table全表扫描
- 推荐SELECT * FROM table WHERE day_id = '${day_id}'
5. 动态分区写入设置
- SET spark.sql.partitionOverwriteMode = dynamic;
- 避免误覆盖其他分区
6. 分区数据清理
- 定期清理历史分区如保留近90天
- 使用 ALTER TABLE DROP PARTITION
*/

View File

@@ -0,0 +1,160 @@
-- =====================================================================
-- @SparkSqlName: PAIMONA-D-SQL-{表名}-QUERY
-- @Version: 1.0
-- @Desc: 标准 SELECT 查询模板
-- @TargetTables: 无(查询输出)
-- @SourceTables: {源表列表}
-- @TargetDatabase: Paimon
-- @SourceDatabase: Paimon
-- =====================================================================
-- ============================================================================
-- 基础查询示例
-- ============================================================================
-- 1. 单表查询
SELECT
id,
name,
amount,
created_at
FROM source_table
WHERE day_id = '${day_id}' -- 分区过滤
AND status = 'active' -- 业务过滤
ORDER BY created_at DESC
LIMIT 1000;
-- ============================================================================
-- JOIN 查询示例
-- ============================================================================
-- 2. 两表 JOIN
SELECT
a.id,
a.name,
b.category_name
FROM main_table a
JOIN dim_table b ON a.category_id = b.id
WHERE a.day_id = '${day_id}'
AND b.is_active = true;
-- 3. 多表 JOIN带别名
SELECT
o.order_id,
u.user_name,
p.product_name,
oi.quantity,
oi.unit_price
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.day_id = '${day_id}'
AND o.status IN ('completed', 'shipped');
-- ============================================================================
-- 聚合查询示例
-- ============================================================================
-- 4. GROUP BY 聚合
SELECT
department,
COUNT(*) AS employee_count,
SUM(salary) AS total_salary,
AVG(salary) AS avg_salary,
MAX(salary) AS max_salary,
MIN(salary) AS min_salary
FROM employees
WHERE day_id = '${day_id}'
GROUP BY department
HAVING COUNT(*) >= 5
ORDER BY total_salary DESC;
-- 5. 多字段分组 + 去重计数
SELECT
date,
region,
COUNT(*) AS order_count,
COUNT(DISTINCT user_id) AS unique_users,
SUM(amount) AS total_amount
FROM orders
WHERE day_id = '${day_id}'
GROUP BY date, region;
-- ============================================================================
-- 窗口函数示例
-- ============================================================================
-- 6. ROW_NUMBER分组取Top N
SELECT *
FROM (
SELECT
department,
name,
salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS rn
FROM employees
WHERE day_id = '${day_id}'
) t
WHERE rn <= 3; -- 每个部门薪资前3名
-- 7. 累计聚合
SELECT
date,
amount,
SUM(amount) OVER (ORDER BY date) AS cumulative_amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS moving_avg_7d
FROM daily_sales
WHERE day_id = '${day_id}';
-- 8. LAG/LEAD环比计算
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS prev_amount,
amount - LAG(amount, 1) OVER (ORDER BY date) AS daily_change,
ROUND((amount - LAG(amount, 1) OVER (ORDER BY date))
/ LAG(amount, 1) OVER (ORDER BY date) * 100, 2) AS growth_rate_pct
FROM daily_sales
WHERE day_id = '${day_id}';
-- ============================================================================
-- 子查询示例
-- ============================================================================
-- 9. IN 子查询
SELECT *
FROM orders
WHERE user_id IN (
SELECT id FROM users WHERE vip_level >= 3
)
AND day_id = '${day_id}';
-- 10. EXISTS 子查询
SELECT *
FROM products p
WHERE EXISTS (
SELECT 1 FROM inventory i
WHERE i.product_id = p.id
AND i.quantity > 0
)
AND p.day_id = '${day_id}';
-- ============================================================================
-- 条件聚合示例
-- ============================================================================
-- 11. CASE WHEN + 聚合
SELECT
date,
COUNT(*) AS total_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN amount > 1000 THEN amount ELSE 0 END) AS high_value_amount
FROM orders
WHERE day_id = '${day_id}'
GROUP BY date;

249
skills/ov-add-data/SKILL.md Normal file
View File

@@ -0,0 +1,249 @@
---
name: ov-add-data
description: This skill adds data(like resources) to OpenViking Context Database (aka. ov). Use when an agent needs to add files, data from URLs, or external knowledge during interactions. Trigger this tool when 1. is explicitly requested adding files or knowledge; 2. identifies valuable resources worth importing; 3. the user mentioned adding to OV/OpenViking/Context Database. This skill helps how to use CLI like `ov add-resource`, `ov add-skill` and `ov add-memory` to add resource data, skill files, memory files to OpenViking.
compatibility: OpenViking CLI configured at `~/.openviking/ovcli.conf`
---
# OpenViking (OV) `add-resource`
The `ov add-resource` command imports external resources into OpenViking's context database — supporting local files, directories, URLs, and remote repositories. Resources are automatically processed with semantic analysis and organized under the `viking://resources/` namespace.
## When to Use
- Importing project documentation, code repositories, or reference materials
- Adding web pages, articles, or online resources for future retrieval
- Building a knowledge base from external sources
- When an agent encounters valuable content that should persist across sessions
- Recording a project's product documentation, design specs, or other valuable materials
- Storing sensitive information or private data of the user, like photo and albums
## CLI Options
### Basic Usage
Import a local file or URL:
```bash
# add a code repo from github or gitlab or a git address
ov add-resource https://github.com/volcengine/OpenViking
ov add-resource https://code.xxxx.org/viking/viking
ov add-resource git@code.xxxx.org:viking/viking.git
# add a file from url
ov add-resource https://arxiv.org/pdf/2602.09540
ov add-resource https://raw.githubusercontent.com/volcengine/OpenViking/main/README.md
# add a file from local filesystem
ov add-resource ./docs/api-spec.md
ov add-resource ./team_building.jpg
ov add-resource /User/volcengine/Documents/profile.pdf
ov add-resource /User/volcengine/Documents/project.docx
# add a zip file from local filesystem (will be unzipped on server)
ov add-resource ./docs-of-project.zip
# add a directory from local filesystem
ov add-resource /User/volcengine/Photo/Travels/2026/ --include "*.jpg,*.jpeg,*.png"
ov add-resource /User/volcengine/Documents/OV项目设计文档/
```
### Context and Instructions (TBD)
Add metadata to guide processing: --reason and --instruction will be supported in the future.
### Async Processing Control
The time of adding resources could cost long (like minutes), and the semantic processing could be async. Use `--wait` to wait for the processing to complete.
```bash
# Wait until semantic processing finishes
ov add-resource ./docs --wait
# Wait with timeout (in seconds)
ov add-resource https://example.com/docs --wait --timeout 60
# Fire and forget (default, without --wait)
ov add-resource ./docs
```
If you fire and forget, the command will return after the resource is completely downloaded/uploaded on server, and then the CLI will return the root URI of the imported resource. To check the status of the resource, you can use `ov ls` or `ov tree` to list the resources under root URI or `viking://resources/`.
### Specify the target path
By default, resources are imported under `viking://resources/`. Use `--to` or `--parent` to specify a target uri.
```bash
# The data will be imported as viking://resources/2026/2026-01-01/, which should not exist before
ov add-resource /User/volcengine/Photo/Travels/2026/2026-01-01/ --to "viking://resources/2026/2026-01-01/"
# The data will be imported under viking://resources/2026, which should exist and be a directory
ov add-resource /User/volcengine/Photo/Travels/2026/2026-01-02/ --parent "viking://resources/2026/"
```
## CLI Output
Returns the root URI of the imported resource, like:
```
root_uri viking://resources/2026/2026-01-01
```
## CLI Prerequisites
- CLI configured in: `~/.openviking/ovcli.conf`
- Network access for the importing URL from server
- Local read access for the importing local files/directories from CLI.
# OpenViking (OV) `add-memory`
The `ov add-memory` command adds long persistant memory — turning text and structured conversations into searchable, retrievable memories in the OpenViking context database. Use `ov add-memory --help` for latest usage.
## When to Use
- After learning something worth remembering across sessions
- To persist conversation insights, decisions, or findings
- To build up a knowledge base from interactions
- When an agent wants to store context for future retrieval
## Input Modes
choose wisely between plain text and multi-turn mode. Multi-turn mode can contain more complex insights, let openviking handle the memory extraction.
### Mode 1: Plain Text for compressed memory
A simple string is stored as a `user` message:
```bash
ov add-memory "User's name is Bob, he participate in Global Hackathon in 2025-01-08, and won Champion."
```
### Mode 2: Multi-turn Conversation for Richer Context
A JSON array of `{role, content}` objects to store a full exchange:
```bash
ov add-memory '[
{"role": "user", "content": "I love traveling. Give me some options of Transport from Beijing to Shanghai."},
{"role": "assistant", "content": "You can use train, bus, or plane. Train is the fastest, but you need to book in advance. Bus is cheaper, but you need to wait. Plane is the most expensive, but you can get there any time of day."},
{"role": "user", "content": "I prefer train. I like sightseeing on the train. Can you give me the train schedule?"},
< ... more possible conversation about schedule and tickest need to be memorized ... >
]'
```
## Output
Returns `OK` once the request is accepted. Memory extraction runs asynchronously on the server after the commit is queued, so the CLI does not wait for extraction to finish:
```
OK
```
## Agent Best Practices
### How to Write Good Memories
1. **Be specific** — Include concrete details, not vague summaries
2. **Include context** — Why this matters, when it applies
3. **Use structured format** — Separate the what from the why
### Batch Related Facts
Group related memories in one call rather than many small ones:
```bash
ov add-memory '[
{"role": "user", "content": "Key facts about the ov_cli Rust crate"},
{"role": "assistant", "content": "1. runs faster than python cli\n2. uses HttpClient to connect openviking server\n3. Output formatting supports table and JSON modes\n4. Config lives at ~/.openviking/ovcli.conf"}
]'
```
## Prerequisites
- CLI configured: `~/.openviking/ovcli.conf`
# OpenViking (OV) `add-skill`
The `ov add-skill` command adds agent capabilities to OpenViking — supporting SKILL.md files, MCP tool definitions, and raw skill content. Skills are automatically processed and organized under the `viking://agent/skills/` namespace, making them discoverable and usable by agents.
## When to Use
- Adding custom agent capabilities and workflows
- Importing MCP (Model Context Protocol) tools
- Persisting skill definitions across agent sessions
- Building a library of reusable agent capabilities
- When an agent needs to extend its toolset with custom logic
## Input Formats
### Mode 1: SKILL.md File
Import from a single SKILL.md file with YAML frontmatter:
```bash
# Add from a single SKILL.md file
ov add-skill ./skills/my-skill/SKILL.md
# Add from a directory containing SKILL.md (includes auxiliary files)
ov add-skill ./skills/my-skill/
```
## CLI Options
### Async Processing Control
Semantic processing happens asynchronously. Use `--wait` to block until complete:
```bash
# Wait until semantic processing finishes
ov add-skill ./skills/my-skill/ --wait
# Wait with timeout (in seconds)
ov add-skill ./skills/my-skill/ --wait --timeout 60
# Fire and forget (default, without --wait)
ov add-skill ./skills/my-skill/
```
## CLI Output
Returns the URI of the added skill, like:
```
uri viking://agent/skills/my-skill/
```
## SKILL.md Format
Skills use Markdown with YAML frontmatter:
```markdown
---
name: skill-name
description: Brief description of the skill
allowed-tools:
- Tool1
- Tool2
tags:
- tag1
- tag2
---
## Including Auxiliary Files
When adding from a directory, all files in the directory are included as auxiliary files:
```bash
# Directory structure:
# ./skills/code-runner/
# ├── SKILL.md
# ├── helper.py
# └── templates/
# └── script.py
ov add-skill ./skills/code-runner/
# Both helper.py and templates/ are included
```
## CLI Prerequisites
- CLI configured: `~/.openviking/ovcli.conf`
- The skill file (SKILL.md) should be in the correct markdown format.

View File

@@ -0,0 +1,157 @@
---
name: ov-search-context
description: Search context data(memories, skills and resource) from OpenViking Context Database (aka. ov). Trigger this tool when 1. need information that might be stored as memories, skills or resources on OpenViking; 2. is explicitly requested searching files or knowledge; 3. sees `search context`, `search openviking`, `search ov` request.
compatibility: CLI configured at `~/.openviking/ovcli.conf`
---
# OpenViking (OV) context searching
The `ov search` command performs context-aware retrieval across all memories and resources in OpenViking — combining semantic understanding with directory recursive retrieval to find the most relevant context for any query.
## Table of Content
- When to Use
- Sub-commands for search
- List directories (`ov ls`)
- Tree view (`ov tree`)
- Semantic Search (`ov find`)
- Content Pattern Search (`ov grep`)
- File Glob Search (`ov glob`)
- Full content read (`ov read`)
- Get overview (`ov overview`)
- Get Abstract (`ov abstract`)
- Prerequisite
## When to Use
- Finding specific information within imported resources or saved memories
- Retrieving context about topics, APIs, or patterns previously added
- Searching across project documentation, code, and learnings
- When an agent needs to reference previously stored knowledge
> note: cli command can be outdated, when sees error, use `--help` to get latest usage
## Sub-commands for search
### List Contents (`ov ls`)
Browse directory structure:
```bash
# List root directory
ov ls
# List specific directory
ov ls viking://resources/my-project/docs/
# Simple path output (only uris, no metadata)
ov ls viking://resources --simple
# Show hidden files
ov ls viking://resources --all
# Control output limits (default 256)
ov ls viking://resources --node-limit 50
# Control abstract info length limit for each node (default 256)
ov ls viking://resources --abs-limit 128
```
### Tree View (`ov tree`)
Visualize directory hierarchy:
```bash
# Show tree structure
ov tree viking://resources
# Control depth limits (default 3)
ov tree viking://resources --level-limit 2
# Control node limits
ov tree viking://resources --node-limit 100 --abs-limit 128
# Show all files including hidden
ov tree viking://resources --all
```
### Semantic find (`ov find`)
Find method with semantic relevance ranking:
```bash
# Basic find across all context
ov find "how to handle API rate limits"
# Find within specific URI scope
ov find "authentication flow" --uri "viking://resources/my-project"
# Limit results and set relevance score threshold
ov find "error handling" --node-limit 5 --threshold 0.3
```
### Content Pattern Search (`ov grep`)
Literal pattern matching:
```bash
# Find exact text pattern (Note: this is expensive, and suggest within specific small URI scope)
ov grep "viking://resources" "TODO:" --uri "viking://resources/my-project"
# Case-insensitive search
ov grep "viking://resources" "API_KEY" --ignore-case --uri "viking://resources/my-project"
# Limit results and set node limit
ov grep "viking://resources" "API_KEY" --node-limit 5 --uri "viking://resources/my-project"
```
### File Glob Search (`ov glob`)
File path pattern matching:
```bash
# Find all markdown files (Note: this is expensive, and suggest within specific small URI scope)
ov glob "**/*.md" --uri "viking://resources/my-project"
# Limit results and set node limit
ov glob "**/*.md" --uri "viking://resources/my-project" --node-limit 5
```
### Read File Content (`ov read`)
Retrieve full content (L0-L2 layer):
```bash
# Read full content
ov read viking://resources/docs/api/api-1.md
# Read first 10 lines of api-2.md
ov read viking://resources/docs/api/api-2.md | head -n 10
# Read abstract (L0 - quick summary)
ov abstract viking://resources/docs/api/
ov read viking://resources/docs/api/.abstract.md
# Read overview (L1 - key points)
ov overview viking://resources/docs/api/
ov read viking://resources/docs/api/.overview.md
```
### Combining Search
Use search results to guide further actions:
```bash
ov ls viking://resources/
# Search for relevant files
ov search "authentication" --uri "viking://resources/project-A"
# Get overview for context
ov overview viking://resources/project-A/backend
# Decide to read specific content
ov read viking://resources/project-A/backend/auth.md
```
## Prerequisites
- CLI configured: `~/.openviking/ovcli.conf`
- Resources or memories previously added to OpenViking

View File

@@ -1,13 +1,13 @@
---
name: smart-data-developer
description: 智能数据开发员工。端到端将数据需求转化为可执行SQL。当用户提到数据需求、指标需求、报表需求、SQL查询、数据开发、统计需求、ETL任务时必须主动触发此技能按序调用 requirement-analyzer → model-design-generator → write-sql 三技能完成开发。
description: 智能数据开发员工。本项目为数据开发专用项目唯一任务是将业务需求转化为可执行SQL。【任何用户消息都必须立即触发此技能】。无论是需求描述、问题咨询、闲聊问候、或其他任何形式的用户输入都应触发此技能启动需求分析→模型设计→SQL编写的工作流程。此技能是本项目的核心入口无例外、无条件触发。
---
# 智能数据开发员工
## 技能定位
按序调用三个子技能完成端到端数据开发。**不体现角色身份**,确认发言自然融入工作流程。
按序调用子技能完成端到端数据开发。
| 步骤 | 子技能 | 产出物 |
|-----|--------|--------|
@@ -17,44 +17,361 @@ description: 智能数据开发员工。端到端将数据需求转化为可执
---
## 工作流程
## Conda 环境配置
不同子技能和 OV 检索需要在不同 conda 环境中运行:
| 功能 | 环境 | 激活方式 |
|-----|------|---------|
| data-structure-fetcher数据源匹配 | `my_opencode` | `conda run -n my_opencode python script.py` |
| ov 检索OpenViking 知识库) | `py13` | `conda run -n py13 ov ...` |
### conda run 示例
**数据源匹配my_opencode 环境)**
```bash
# 直接使用 conda run 执行 Python 脚本
conda run -n my_opencode python /path/to/data_fetcher_script.py
# 或先激活环境再执行
conda activate my_opencode
python /path/to/data_fetcher_script.py
```
需求 → [步骤1] → [确认] → [步骤2] → [确认] → [步骤3] → 交付
**OV 检索py13 环境)**
```bash
# 精确 grep 搜索表结构
conda run -n py13 ov grep "dim_intnl_org" --uri "viking://resources/table-metadata" --node-limit 5
# 语义 find 搜索成功案例
conda run -n py13 ov find "窗口函数" --uri "viking://resources/sql_snippets" --threshold 0.2
# 读取资源内容
conda run -n py13 ov read viking://resources/sql_snippets/{目录}/{文件}.sql
```
---
## 执行流程
## OpenViking 资源库
加载`ov-search-context`技能来查找数据开发相关的资源
### 核心资源
| 资源路径 | 用途 | 搜索策略 |
|---------|------|---------|
| `viking://resources/sql_snippets` | 成功SQL代码片段 | 语义find + 精确grep |
| `viking://resources/table-metadata` | 表元信息(表结构、字段定义) | 精确grep + 语义find |
| `viking://resources/field-process-memory` | 字段处理经验笔记 | 语义find + 精确grep |
### 搜索参数推荐
| 参数 | 推荐值 | 说明 |
|-----|-------|------|
| `--node-limit` | 5-15 | 避免上下文膨胀 |
| `--threshold` | 0.2 | 过滤低相关性结果(语义搜索) |
---
## OV搜索强制执行时机
**为什么必须 OV 搜索**OV 知识库包含历史成功案例、表结构信息、字段处理经验。**不检索会导致**
- 猜测表结构 → 字段选错 → SQL 失败
- 缺乏成功案例参考 → 重复踩坑 → 代码质量差
- 猜测字段含义 → 口径偏差 → 业务错误
- 猜测处理方式 → 维护困难 → 返工成本高
---
**强制触发时机(不可跳过)**
| 时机 | 触发点 | 搜索内容 | 强制性 | 检索数量 |
|-----|--------|---------|--------|---------|
| 步骤1 - 数据源匹配后 | data-structure-fetcher 返回源表列表后 | **③ 常用数据源判断(最优先)** + 每个推荐表执行 5 类检索表结构、成功SQL、常用判断、字段处理经验、业务语义 | ✅ MUST | 每表 5 次 |
| 步骤1 - 核心字段确认后 | 用户确认核心字段时 | 对核心字段进行专项检索 | ✅ MUST | 每字段 3 次 |
| 步骤2 - 模型设计前 | 用户确认需求后,开始模型设计 | OV 检索分层规范、参考案例 | ✅ MUST | 2-3 次 |
| 步骤3 - SQL 编写前 | 用户确认模型设计后,开始编写 SQL | OV 检索语法、成功案例 | ✅ MUST | 3-5 次 |
---
**数据源匹配后的具体检索内容**
| 检索类型 | 目的 | 命令py13 环境) | 优先级 |
|---------|------|-----------------|--------|
| ③ 常用数据源判断 | **判断是否为常用数据源**(历史使用频率、稳定性) | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/field-process-memory" --node-limit 10` | **最高(立即执行)** |
| ① 表结构检索 | 确认字段定义 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata" --node-limit 10` | 高 |
| ② 成功SQL检索 | 找历史代码案例 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/sql_snippets" --node-limit 15` | 高 |
| ④ 字段处理经验 | 了解处理方式 | `conda run -n py13 ov grep "{表名}.*{字段}" --uri "viking://resources/field-process-memory" --node-limit 10` | 中 |
| ⑤ 业务语义检索 | 业务使用经验 | `conda run -n py13 ov find "{表名} {业务}" --uri "viking://resources/field-process-memory" --threshold 0.2` | 中 |
---
**困惑场景(立即检索)**
- 表名陌生 → 立刻 `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata"`
- 字段歧义 → 立刻 `conda run -n py13 ov grep "{表名}.*{字段}" --uri "viking://resources/table-metadata"`
- 语法不确定 → 立刻 `conda run -n py13 ov find "{语法}" --uri "viking://resources/sql_snippets" --threshold 0.2`
- 找不到成功案例 → 立刻 `conda run -n py13 ov find "{业务场景}" --uri "viking://resources/sql_snippets" --threshold 0.2`
---
## OV命令速查
**注意OV 检索需要在 `py13` conda 环境中运行,使用 `conda run -n py13 ov ...`**
```bash
# === 精确grep定位具体表/字段 ===
用表名搜索:
conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata" --node-limit 5
表名加字段名组合搜索:
conda run -n py13 ov grep "dim_intnl_org.*org_id" --uri viking://resources/table-metadata -n 5
多表名 OR 搜索:
conda run -n py13 ov grep "table1|table2|table3" --uri viking://resources/table-metadata -n 5
# === 语义find召回相关内容 ===
conda run -n py13 ov find "{业务概念}" --uri "viking://resources/sql_snippets" --threshold 0.2
conda run -n py13 ov find "{问题描述}" --uri "viking://resources/field-process-memory" --threshold 0.2
# === 使用源表信息搜索成功案例 ===
conda run -n py13 ov grep "{源表表名}" --uri "viking://resources/sql_snippets" --node-limit 10
conda run -n py13 ov grep "{源表表名}.*{字段}" --uri "viking://resources/field-process-memory" --node-limit 10
conda run -n py13 ov find "{字段名} 问题描述" --uri "viking://resources/field-process-memory" --node-limit 5
# === 读取内容 ===
conda run -n py13 ov overview viking://resources/table-metadata/{表名}
conda run -n py13 ov read viking://resources/sql_snippets/{目录}/{文件}.sql
conda run -n py13 ov read viking://resources/field-process-memory/{文件}.md
```
---
## 工作流程
```
步骤1需求分析 → 调用 requirement-analyzer + 数据源匹配的同时进行OV检索 + 困惑时OV检索
步骤2模型设计 → 调用 model-design-generator + 困惑时OV检索
步骤3SQL编写 → 调用 write-sql + 困惑时OV检索
交付完成
```
---
## 执行流程详细步骤
### 步骤1需求分析
**执行前检查**:无(直接开始)。
调用 `skill(name="requirement-analyzer")`
**子技能完成后,自然衔接**
```
📄 已保存:{路径}
**困惑时检索py13 环境)**
- 不熟悉业务概念 → `conda run -n py13 ov find "{概念}" --uri "viking://resources/field-process-memory" --threshold 0.2`
- 不确定指标口径 → `conda run -n py13 ov find "{指标名称} 口径" --uri "viking://resources/field-process-memory" --threshold 0.3`
下一步将生成模型设计。请确认以上需求是否正确,或指出需要修改的内容。
**完成后衔接**`📄 已保存:{路径}。需求分析完成,开始模型设计...`
#### ✅ MUST数据源匹配后 OV 深度检索
**强制性说明**data-structure-fetcher 返回推荐数据源后,**必须对每个推荐表进行全方位 OV 检索**。不检索会导致:
- 猜测表结构 → 字段选错 → SQL 失败
- 缺乏成功案例参考 → 重复踩坑 → 代码质量差
- 不了解字段处理经验 → 口径偏差 → 业务错误
---
**触发时机**requirement-analyzer 模块3data-structure-fetcher 返回推荐表列表后,立即执行
**执行环境**py13 conda 环境,使用 `conda run -n py13 ov ...`
---
**检索策略:每个推荐表必须执行以下 5 类检索**
| 检索类型 | 目的 | 命令模板 | node-limit |
|---------|------|---------|------------|
| ① 表结构检索 | 确认表字段定义、数据类型 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata"` | 5-10 |
| ② 成功SQL案例检索 | 查找该表的历史成功 SQL 代码 | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/sql_snippets"` | 10-15 |
| ③ 常用数据源判断 | **判断该表是否为常用数据源**(历史使用频率、稳定性、推荐度) | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/field-process-memory"` | 5-10 |
| ④ 字段处理经验检索 | 了解该表字段的常见处理方式、注意事项 | `conda run -n py13 ov grep "{表名}.*{字段名}" --uri "viking://resources/field-process-memory"` | 5-10 |
| ⑤ 业务语义检索 | 了解该表在业务场景中的使用经验 | `conda run -n py13 ov find "{表名} {业务场景}" --uri "viking://resources/field-process-memory" --threshold 0.2` | 5-10 |
---
**③ 常用数据源判断 - 详细说明**
这是数据源匹配后**必须立即执行**的关键检索,用于判断推荐表是否值得采用:
**执行时机**data-structure-fetcher 返回推荐表后,**立即执行此检索**
**检索命令**
```bash
conda run -n py13 ov grep "{表名}" --uri "viking://resources/field-process-memory" --node-limit 10
```
**判断标准**
| 检索结果 | 判断 | 建议操作 |
|---------|------|---------|
| 有多条历史使用记录≥3条 | ✅ **常用数据源** | 推荐优先采用,可参考历史处理经验 |
| 有少量记录1-2条 | ⚠️ **偶尔使用** | 可采用,需谨慎验证字段逻辑 |
| 无检索结果 | ❓ **新/陌生数据源** | 需进一步验证表结构,或寻找替代表 |
| 有警告/问题记录 | ⚠️ **有风险** | 需评估问题严重性,考虑替代方案 |
**结果展示格式**
```
📋 常用数据源判断结果:
【dwd_crm_srv_complaint_rt】
- 检索结果:找到 5 条历史使用记录
- 判断:✅ 常用数据源(推荐采用)
- 历史使用场景:投诉量统计、投诉处理时效、部门投诉排名
- 注意事项create_org_id 需关联组织表获取部门名称
【dim_intnl_org_new】
- 检索结果:找到 8 条历史使用记录
- 判断:✅ 常用数据源(高频使用)
- 历史使用场景:组织维度统计、部门层级分析、归属地统计
- 注意事项org_name 有多级命名,需按业务口径标准化
```
**决策建议**
- 多个推荐表中,优先选择"常用数据源"
- 对"陌生数据源"需额外验证,或向用户确认是否采用
- 对"有风险"数据源,建议告知用户并提供替代方案
---
**具体执行流程py13 环境)**
```bash
# 对每个推荐表dwd_crm_srv_complaint_rt依次执行
# ① 表结构检索(确认字段)
conda run -n py13 ov grep "dwd_crm_srv_complaint_rt" --uri "viking://resources/table-metadata" --node-limit 10
# ② 成功SQL案例检索找历史代码
conda run -n py13 ov grep "dwd_crm_srv_complaint_rt" --uri "viking://resources/sql_snippets" --node-limit 15
# ③ 常用数据源判断(判断是否常用 - 数据源匹配后立即执行)
conda run -n py13 ov grep "dwd_crm_srv_complaint_rt" --uri "viking://resources/field-process-memory" --node-limit 10
# ④ 字段处理经验检索(找处理笔记 - 对核心字段)
conda run -n py13 ov grep "dwd_crm_srv_complaint_rt.complaint_id" --uri "viking://resources/field-process-memory" --node-limit 10
# ⑤ 业务语义检索(结合业务场景)
conda run -n py13 ov find "dwd_crm_srv_complaint_rt 投诉统计" --uri "viking://resources/field-process-memory" --threshold 0.2 --node-limit 10
```
**执行顺序建议**
- ③ 常用数据源判断是**最优先执行**的检索,应在数据源匹配后立即执行
- 其他检索可根据常用数据源判断结果调整执行优先级
---
**检索结果利用**
检索完成后,必须:
1. **汇总展示**:将检索结果整理成摘要,展示给用户
```
📊 OV 检索结果摘要:
【dwd_crm_srv_complaint_rt】
- 常用数据源:✅ 常用(找到 5 条历史使用记录)
- 表结构:包含 complaint_id、create_org_id、assist_dept_org_id 等字段
- 成功案例:找到 3 个历史 SQL投诉量统计、投诉处理时效分析
- 处理经验create_org_id 字段需关联组织架构表获取部门名称
【dim_intnl_org_new】
- 常用数据源:✅ 常用(找到 8 条历史使用记录)
- 表结构:包含 org_id、org_name、accnt_bureau 等字段
- 成功案例:找到 5 个历史 SQL部门维度统计
- 处理经验org_name 有多级命名规范,需按业务口径标准化
【dwd_new_table_2024】
- 常用数据源:❓ 陌生数据源(无历史使用记录)
- 建议:需进一步验证表结构,或寻找更成熟的替代表
```
2. **辅助源表选择**:基于常用数据源判断和成功案例数量,建议用户优先选择哪些表
```
建议:
- ✅ 优先采用dwd_crm_srv_complaint_rt常用数据源有 3 个成功案例,历史使用稳定)
- ✅ 优先采用dim_intnl_org_new常用数据源高频使用组织表标准参考
- ⚠️ 需验证dwd_new_table_2024陌生数据源建议先验证或寻找替代表
```
3. **预加载到上下文**:检索到的成功案例和字段处理经验,需在后续模型设计和 SQL 编写时引用
---
**检索时机强制触发表**
| 时间点 | 触发条件 | 检索内容 | 强制性 |
|-------|---------|---------|--------|
| data-structure-fetcher 返回后 | 推荐表列表返回 | **③ 常用数据源判断(最优先)** + 每个表的 5 类检索 | ✅ MUST |
| 用户补充新表名时 | 用户提及陌生表名 | 立刻 OV 检索该表(优先执行常用数据源判断) | ✅ MUST |
| 用户询问字段含义时 | 用户对字段有疑问 | OV grep 该表.字段 | ✅ MUST |
| 发现字段歧义时 | 同一字段有多来源 | OV find 语义检索 | ✅ MUST |
| 陌生数据源判断后 | 无历史使用记录 | 额外验证表结构,或建议替代表 | ✅ MUST |
---
**并行检索策略**
为提高效率,对多个推荐表的检索可并行执行:
```bash
# 并行检索多个表(使用 task agent 或 bash 后台)
conda run -n py13 ov grep "table1" --uri "viking://resources/table-metadata" --node-limit 10 &
conda run -n py13 ov grep "table2" --uri "viking://resources/table-metadata" --node-limit 10 &
conda run -n py13 ov grep "table3" --uri "viking://resources/table-metadata" --node-limit 10 &
```
---
**关键字段专项检索**
当推荐表中包含**核心业务字段**时,必须额外进行字段级检索:
```bash
# 对核心字段complaint_id、org_id进行专项检索
conda run -n py13 ov grep "dwd_crm_srv_complaint_rt.complaint_id" --uri "viking://resources/table-metadata" --node-limit 5
conda run -n py13 ov grep "complaint_id" --uri "viking://resources/field-process-memory" --node-limit 10
conda run -n py13 ov find "complaint_id 去重计数" --uri "viking://resources/sql_snippets" --threshold 0.2 --node-limit 10
```
---
### 步骤2模型设计
**确认后进入**
调用 `skill(name="model-design-generator")`,传入需求文档路径
**执行前检查**:需求分析已完成,用户已确认需求。
**子技能完成后,自然衔接**
```
📄 已保存:{路径}
调用 `skill(name="model-design-generator")`
下一步将编写SQL脚本。请确认以上设计是否正确或指出需要修改的内容。
```
**困惑时检索py13 环境)**
- 确认表结构 → `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata"`
- 了解分层规范 → `conda run -n py13 ov find "分层架构 DWA DWM ADS" --uri "viking://resources/sql_snippets" --threshold 0.2`
**完成后衔接**`📄 已保存:{路径}。下一步将编写SQL脚本。请确认以上设计是否正确。`
---
### 步骤3SQL编写
**确认后进入**
调用 `skill(name="write-sql")`,传入需求文档和模型设计文档
**执行前检查**:用户已确认模型设计
**完成后自然衔接**
调用 `skill(name="write-sql")`
**困惑时检索py13 环境)**
- SQL语法 → `conda run -n py13 ov find "{语法} 用法" --uri "viking://resources/sql_snippets" --threshold 0.2`
- 窗口函数示例 → `conda run -n py13 ov find "窗口函数 over partition" --uri "viking://resources/sql_snippets" --threshold 0.2`
**完成后衔接**
```
📄 已保存:{路径}
@@ -63,16 +380,20 @@ description: 智能数据开发员工。端到端将数据需求转化为可执
- 模型设计:{路径}
- SQL脚本{路径}
```
---
## 状态跟踪
```python
state = {
"step": 0, # 0→1→1.5(等待)→2→2.5(等待)→3→4(完成)
"confirmed": [False, False], # [步骤1确认, 步骤2确认]
"paths": {"req": None, "model": None, "sql": None}
}
```
- step 1: 需求分析完成 → 准备模型设计
- step 2: 模型设计完成 → 等待用户确认
- step 3: SQL编写完成
- step 4: 全流程完成
记录产出物路径需求文档、模型设计文档、SQL脚本。
---
## 交互规则
| 场景 | 响应 |
@@ -80,9 +401,51 @@ state = {
| 用户确认 | 进入下一步 |
| 用户修改意见 | 调整后重新等待确认 |
| 中途修改 | 回到对应步骤重新执行 |
| 用户提及陌生概念 | 先OV检索再回复提供选项 |
---
## 交互工具
在需要补充和确认信息,检查和修改的时候,使用`question`工具提供选项供用户选择,例如下面的情况:
```
例如下面的情况,请使用`question`工具:
请补充/确认以下信息:
1. 输出表中文名称:建议为"设备流量通话短信月均使用情况表",是否需要修改?
2. 输出表英文名称:请提供库名.表名db_eda_xxx_prd.ads_device_usage_avg_6m
3. 数据目录:请提供数据目录路径(如:上海电信/大数据中心-xxx团队/xxx应用层
4. 业务口径细节确认:
- "近6个月"的具体时间范围是指从当月向前推6个月即统计月及之前5个月共6个月
- "平均值"计算方式是计算每个设备在这6个月的平均值还是其他口径
- 设备标识字段是什么设备号、用户ID、手机号等
请确认或补充以上信息确认后进入模块3数据源匹配
```
```
请检查并修改:
- 字段是否完整?需要补充或删除哪些字段?
- 字段名称是否需要调整?
- 字段加工逻辑是否准确?来源表.字段是否正确?
- 负责人信息是否正确?
```
---
## 困惑场景检索策略
**注意:所有 OV 检索命令需在 py13 conda 环境中运行**
| 困惑类型 | 检索资源 | 命令 |
|---------|---------|------|
| 不清楚表结构 | table-metadata | `conda run -n py13 ov grep "{表名}" --uri "viking://resources/table-metadata"` |
| 不确定字段含义 | table-metadata | `conda run -n py13 ov grep "{表名}.*{字段名}" --uri "viking://resources/table-metadata"` 或者 `conda run -n py13 ov grep "{字段名}" --uri "viking://resources/table-metadata"` |
| 不熟悉业务概念 | sql_snippets + field-process-memory | `conda run -n py13 ov find "{概念}" --uri "viking://resources/resources/field-process-memory" --threshold 0.2` |
| 需要代码参考 | sql_snippets + field-process-memory | `conda run -n py13 ov find "{功能}" --uri "viking://resources/sql_snippets" --threshold 0.2` |
| 找表成功案例 | sql_snippets + field-process-memory | 分别使用 `conda run -n py13 ov grep` 和 `conda run -n py13 ov find` 搜索两个资源 |
---
## 完成标志
三步骤完成,用户已知交付物位置。
三步骤完成(需求分析 → 模型设计 → SQL编写,用户已知交付物位置。