Add one-skill

This commit is contained in:
Xin Wang
2026-05-13 11:03:00 +08:00
parent a4c8b29176
commit f9e36ef92d
34 changed files with 7656 additions and 0 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,413 @@
---
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/table-metadata` | 表元信息(表结构、字段定义) | 精确 grep + 语义 find |
| `viking://resources/sql_snippets` | 成功 SQL 代码片段 | 语义 find + 精确 grep |
| `viking://resources/field-process-memory` | 字段处理经验、常用数据源判断、业务经验 | 语义 find + 精确 grep |
### OV 搜索强制执行时机
| 时机 | 搜索内容 | 强制性 |
|-----|---------|--------|
| 步骤1 - 数据源匹配 | 对候选表执行表结构、成功 SQL、常用数据源判断、字段处理经验、业务语义检索 | MUST |
| 步骤1 - 核心字段确认后 | 对核心字段进行专项检索 | MUST |
| 步骤2 - 模型设计前 | 检索分层规范、参考案例 | MUST |
| 步骤3 - SQL 编写前 | 检索语法、成功案例、历史 SQL 片段 | MUST |
### OV 优先 + legacy fallback
默认使用 OV 搜索,不主动调用 legacy data-structure-fetcher。只有以下情况才读取 `references/legacy-data-structure-fetcher/SKILL.md` 并运行其脚本:
1. OV 命令失败或本地不可用;
2. OV 搜索返回的候选表不足以进入用户确认;
3. 用户明确要求使用旧数据字典匹配脚本。
---
## 工作流程
```
用户输入需求
[入口] 场景判断 + 用户确认
├── 数据开发 → [步骤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,548 @@
---
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. 读取 `../references/ov-search-context.md` 了解 OV 命令
3. 先用业务关键词语义召回候选表:
- `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`
4. 对每个推荐表执行 5 类 OV 检索:
- 表结构:`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 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`
5. 获取推荐的数据源列表(库、表、字段、技术负责人、字段类型),并给出常用数据源判断
6. 仅当 OV 命令失败、候选表不足、或用户明确要求旧方案时,读取 `../references/legacy-data-structure-fetcher/SKILL.md` 并使用其中脚本补充推荐
**输出格式**
```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 匹配数据源,模块 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,96 @@
---
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. 精确加载一个对应模板:
→ 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
4. 加载引擎生成规则:
→ ../references/sql/reference/{engine}-sql-syntax.md 的「SQL 生成规则」章节
5. 按通用规则 + 引擎特有规则 + 模板骨架 生成 SQL
6. 如有 output_path 则写入文件
```
# 通用规则(所有引擎都必须遵守)
以下规则适用于所有引擎,不受引擎差异影响:
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. **精确加载一个模板**:只加载任务类型对应的那个模板文件,不要加载多个
4. **查阅引擎规则**:读取 `../references/sql/reference/{engine}-sql-syntax.md` 的「SQL 生成规则」章节
5. **解析 context**提取表名、字段、逻辑、过滤条件、聚合需求、JOIN 关系
6. **生成 SQL**:通用规则 + 引擎特有规则 + 模板骨架
7. **自我审查**:检查是否遗漏步骤、是否符合规范
8. **输出**:对话展示 SQL + 如调用方提供了 output_path 则写入纯 SQL 文件
# 硬性约束
1. **不读取文件**write-sql 不负责读取需求文档或模型设计文档,所有内容通过 context 传入
2. **不决定路径**文件输出路径由调用方决定write-sql 只负责写入
3. **必须查阅本 skill 的 SQL 资源**:生成 SQL 前先查阅 `../references/sql/reference/``../references/sql/templates/`
4. **不编造语法**:不确定的语法查阅 reference 确认

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 不可用、召回不足或用户指定旧方案时,按上述流程执行脚本并汇总结果。

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;