# Fetch Data

> 取数 / 查数据 / 拉数据 / 跑 SQL。把自然语言取数需求转为 SQL，经数据湖仓执行后返回查询结果供下游分析。**任何需要业务数据的任务在工作区缺少对应文件时都必须先调用此技能**——覆盖 BI 业务分析、留存 / 转化 / 同期群分析、数据探索 EDA、统计建模、定量计算、元数据查询、数据查询。命中任一即触发：(1) 直接索要指标或记录，如「DAU 多少」「上月销售额」「3 月留存率」「这个客户的订单」；(2) 取数口语，如「查 / 查一下 / 取一下 / 拉一下 / 抓数据 / 找数据 / 搜数据 / 跑 SQL / 写 SQL / 导出 / 缺数据 / 没数据 / 数据不够」；(3) 涉及数据来源，如「从语义层 / 数据湖仓 / 仓库 / 数据库取」；(4) 问元数据 / 口径，如「字段含义 / 表结构 / 这两张表怎么关联 / 可用数据源 / 指标怎么算」；(5) 下游任务在工作区找不到所需数据。

- Skill: `agentscope-ai/fetch-data` (Agent Skill, multi-file: 8 files)
- Install (CLI): `npx skillmds@latest add agentscope-ai/fetch-data`
- Raw SKILL.md: https://api.skillmd.com/api/skills/agentscope-ai/fetch-data/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: agentscope-ai (https://skillmd.com/u/agentscope-ai)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/agentscope-ai/fetch-data

---


# fetch-data

将用户的数据分析问题转为结构化意图，并补全取数所需完整语义上下文，生成 NL2SQL prompt 与 SQL，执行后返回查询结果供下游分析。

## 前置条件

1. 确认用户的数据分析问题**明确**（要分析什么、大致时间/范围）。
2. 检查 `{workdir}` 是否**已包含全部所需分析数据**：
   - **已全部存在** → 直接使用这些文件，**结束取数**，不执行取数流程。
   - **有缺失** → 继续。

---

## 取数流程

> **存储约定**：**落盘生成 NL2SQL prompt 所必需的产物，以及据此产出的 prompt / SQL 本身**。文件命名由各步骤自行决定（建议语义清晰、便于追溯），但**目录不能错位**。
>
> | Step | 产物 | 存储路径 | 性质 |
> |------|------|------|------|
> | 1 | 结构化问题（含 Step 3 改写后的版本） | `{workdir}/steps/` | prompt 输入 |
> | 2.1 | 指标信息 | `{workdir}/data/raw/` | prompt 输入（语义层 / 上下文）|
> | 2.2 | 数据表元数据 | `{workdir}/data/raw/` | prompt 输入（语义层 / 数据湖仓元数据）|
> | 4 | NL2SQL prompt | `{workdir}/steps/` | prompt 产出 |
> | 5 | 生成的 SQL | `{workdir}/steps/` | prompt 下游代码 |

### Step 1. 问题结构化

从用户的自然语言分析问题中抽取字段，结构化产物落盘到 `{workdir}/steps/`（属于步骤过程产物）。

**Schema**（缺失字段填 `null`，不要省略键）：

```json
{
	"question": "<原始用户问题>"	# 用户原始问题
	"domain": "<业务域>",	# 用户问题面向的业务领域/数据来源，如 产品A、产品B 等
	"metrics": [								# 数据分析问题中涉及的关键指标
		"<关键指标1>",							# 如 DAU
		"<关键指标2>"							  # 如日均访问用户数
	],
	"intention": "<分析意图>",		# 用户想要完成的最终分析问题，如“数据在不同端的分布情况”
	"scope": [									# 数据的限定范围，如时间范围、维度等
		"<时间限定>",							# 数据分析针对的时间范畴，如上个月、近3个月等
		"<地域限定>"							  # 数据分析针对的地域范畴，如“中国以及俄罗斯用户”
	]
}
```

| 字段 | 含义 | 示例 |
|------|------|------|
| `question` | 原始问题 | 上个月，中国与俄罗斯用户对某产品的日均访问用户数在各个端的分布情况 |
| `domain` | 业务领域 | 产品A / 产品B / 产品C |
| `metrics` | 关键指标 | DAU、日均访问用户数、次日访问留存率 |
| `intention` | 最终分析问题 | 数据在不同端的分布情况 |
| `scope` | 数据限定范围（时间、地域等） | `["上个月", "中国以及俄罗斯用户"]` |

- 用户未提及的字段 → `null`（`scope` 中未知项也用 `null` 占位或省略该元素，保持数组语义清晰）。
- **`metrics` 写法**：尽量使用数据分析问题中已存在的**标准指标名**（如 `DAU` / `对话用户数` / `次日访问留存率`），不要用自由发挥的同义改写，以免造成歧义，导致后续步骤理解出错。

例如，对于用户输入问题「5月 app端某模型对话用户数，人均对话次数和点赞率分别是多少？」，可解析出如下信息：

```json
{
  "question": "5月 app端某模型对话用户数，人均对话次数和点赞率分别是多少？",
  "domain": "产品A",
  "metrics": ["对话用户数", "人均对话次数", "点赞率"],
  "intention": "查询5月app端某模型的三个指标值：对话用户数、人均对话次数和点赞率",
  "scope": [
    "5月",
    "app端",
    "某模型"
  ]
}
```

### Step 2. 业务数据信息补全

将 Step 1 的结构化问题作为输入，从可用知识源补全取数所需的业务语义。本步骤的产物全部落盘到 `{workdir}/data/raw/`。

> **知识源优先级**（Step 2.1 和 Step 2.2 都遵循）：
>
> 1. **语义层**——首选。语义层维护了被业务方校准过的指标 / 维度 / 表语义，结果可直接使用。
> 2. **数据湖仓元数据**——语义层缺失或不可用时回退。数据湖仓元数据提供的是表 / 列级别的物理事实（表名、列名、dtype、注释），用它补全 Step 2.2 的字段；对于 Step 2.1 的指标语义，需自行结合列注释 / 命名推断。
> 3. **上下文信息**（如对话历史、用户先前提供的口径说明等）——仅当上述两者都拿不到时才允许使用，并且**必须**满足下面两条硬约束：
>    - **精确对应**：上下文里出现的指标名 / 表名 / 列名必须与本次要探查的目标**完全一致**（同名同义、口径一致），不允许拿"看起来差不多"的旧上下文凑数；
>    - **来源标注**：在落盘的 JSON 中，每个来自上下文的字段都要在产物文件里以注释 / 同级 `source: "context"` 字段或同名旁注的方式显式标出，便于 Step 3 复核。
>
> 任一字段若三个来源都拿不到，按"留空"处理，**不得**用模型先验或想象填充。

#### Step 2.1. 指标信息补全

针对 Step 1 中的关键指标，按上述优先级查询其业务定义与计算口径。若 Step 1 中没有成功获取明确的意向指标，则将分析意图作为指标名进行查询。若分析意图也不存在，则回退使用原问题进行查询。

**本步骤所需探查到的信息**（每个关键指标至少应得到如下关键字段）：

```json
{
	"metrics": [														# 查询中所需的指标
		{
			"metric_name": "<指标1名称>",				# 语义层中存储的标准指标名
      "synonyms": [									    # 指标的同义词
        "<同义词1>",
        "<同义词2>"
      ],
      "metric_type": "<指标类型>",					# 指标的类型，如 "quantity" / "ratio" / "percentage"
      "is_north_star": true,						# 是否是北极星指标
      "is_display": true,							# 是否在数据分析中展示
			"formulas": [
				{
					"dataset": "<数据表名1>",				# 能够支持指标计算的数据表
					"formula": "<指标计算方式>"				# 在该数据表中，该指标计算方式
				},
				{
					"dataset": "<数据表名2>",
					"formula": "<指标计算方式>"
				}
			]
		}
	]
}
```

把获取到的指标信息落盘到 `{workdir}/data/raw/`，不遗漏任何探查到的字段信息；若不存在的字段信息，则补充为空，**不可捏造**不存在的信息。

#### Step 2.2. 表元数据补全

针对 Step 2.1 中所有数据表，按上述优先级查询表与列级别的元信息。

**本步骤所需探查到的信息**（每张数据表至少应得到如下关键字段）：

```json
[
	{
		"table_name": "<table name>",											# 查询相关的表名
		"description": "<description of the table>",			# 表的描述
		"columns": [																			# 表中列的元信息
			{
				"column_name": "<column name>",								# 列名
				"dtype": "<type of data>",										# 列中值的类型
				"description": "<description of the column>",	# 列的描述
        "topline_value": "<topline value for the volumn>",  # 列的 topline 值
        "sample_values": "<samples from the column>"  # 列中的采样数值
			}
		]
	}
]
```

**注意**：

- `sample_values` 仅提供部分采样数值，用于辅助理解列的含义，并非精确取值，请查看实际取值时以实际数据为准。
- 若返回的表元数据被截断，则需查看实际数据表的完整元数据，确保不遗漏任何字段信息。
- 对于不存在的字段信息，则补充为空，**不可捏造**不存在的信息。

把获取到的数据表的**完整**表元数据信息落盘到 `{workdir}/data/raw/`。

#### Step 2.3. domain 校验

完成指标 / 表元数据补全后，回头检查 Step 1 结构化问题产物中的 `domain` 字段。`domain` 必须**唯一确定且非空**才能进入后续步骤。

逐项核对：

1. **非空**：`domain` 不是 `null` / 空串 / 占位符。
2. **唯一确定**：`domain` 取值唯一，没有出现"多个业务域并存 / 取值含糊"的情况。可借助 Step 2 探查到的指标 / 表元数据（如表名前缀、`schema.json` 中各表的 `domain` 标注）反向印证结构化问题里的 `domain` 是否自洽。

按校验结果处理：

- **唯一且非空** → 继续 Step 3。若 Step 2 的元数据指向的业务域与结构化问题里的 `domain` 不一致，按"取值含糊"处理（见下）。
- **为空 / 缺失** → **停下并向用户提问**，请其补充本次分析对应的业务域（可结合 Step 2 元数据给出候选域列表供选择），拿到答复后**原地写回** Step 1 结构化问题产物的 `domain` 字段，并在回复正文中记录该补充。**不要**替用户臆测 domain。
- **取值含糊 / 多域并存** → 同样**停下向用户确认**唯一业务域，确认后写回 `domain`。

> domain 校验通过是进入后续步骤的前置条件；`domain` 仍为空时**不得**继续往下走。

### Step 3. 歧义消除

Step 2 完成后，**先停下来**对照原始问题与新得到的指标信息 / 表元数据核一遍：用户的指标 / 维度 / 时间口径在补全后的数据语义里是否还存在**多解**或其他理解偏差。有歧义就**先消除**，再进入 Step 4。本过程**严格**遵循搜索到的数据，**不可捏造**不存在的信息。

**输入**：Step 1 的结构化问题 + Step 2.1 的指标信息 + Step 2.2 的表元数据 + 原始问题
**产物**：
- 消歧结论在**回复正文**中说明（即便“无歧义”也要写），**不落盘**
- 必要时**原地改写** Step 1 在 `{workdir}/steps/` 下的结构化问题产物：把模糊词替换成确认后的标准 `metric_name` / `<列名>=<值>` 形式（改写的原值 / 新值在回复正文中说明）

#### Step 3.1. 分析意图歧义消除

常见分析意图歧义模式与解决方案：参见 [`references/intent_ambiguity.md`](references/intent_ambiguity.md)。逐项检查：

1. **指标指向不明**

用结构化问题中 `metrics` 里的每一项分析指标去和 Step 2.1 探查到的指标信息中的可用指标对比。若**多条**指标可对应同一分析指标（如“留存率”同时命中“访问留存率”、“使用留存率”）→ 有歧义。
- 若存在歧义，则抛出问题以及待确定的选项（即，所有可用指标），让用户确认。
- 若用户确认后，则将确认后的标准 `metric_name` 写回 Step 1 的结构化问题产物。
- 写回后检查该 `metric_name` 是否已存在于 Step 2.1 现有产物中；若不存在（改名 / 切换到新指标），则按 Step 2.1 的指标信息补全步骤重新执行指标信息补全（并按 Step 2.2 补齐新指标所需的表元数据），详见 Step 3.3 的**联动**说明。

2. **指标聚合粒度不明**

对每个已能对齐到标准 `metric_name` 的指标，检查 Step 2.1 现有产物中是否存在**某个 `metric_name`** 所需聚合粒度不明（如“一段时间的对话用户数”同时命中“日均对话用户数”“时段总和对话用户数”），且原始问题未明确选择哪一种 → 有歧义。
- 参考 [`references/intent_ambiguity.md`](references/intent_ambiguity.md) 章节 2 中的默认解决方案。
- 若问题不被覆盖，则抛出问题以及待确定的选项（即，所有可用聚合粒度），让用户确认。
- 若指标明确/用户确认后，查看指标信息是否已获取；若无，则按 Step 2.1 的指标信息补全步骤，重新执行指标信息补全。

#### Step 3.2. 数据来源歧义消除

常见数据来源歧义模式与解决方案：参见 [`references/data_source_ambiguity.md`](references/data_source_ambiguity.md)。逐项检查：

1. **数据表来源不明**

数据中多个数据表在指标计算、数据维度选择等都满足数据分析需求。例如，分析“计算某产品不同端 dau 指标”，表 `xxx_index` 和 `xxx_distribution` 都包含可用于计算 dau 指标都数据，且包含指向不同端的字段，即两张表都可用于数据分析。
- 参考 [`references/data_source_ambiguity.md`](references/data_source_ambiguity.md) 章节 1 中的解决方法。
- 若问题不被覆盖，则抛出问题以及待确定的选项（即，所有可用数据表），让用户确认。
- 若数据表明确/用户确认后，查看数据表元数据是否已获取；若无，则按 Step 2.2 的表元数据补全步骤，重新执行表元数据补全。

2. **数据维度指向不明**

数据中多个维度可用于数据筛选。例如，分析“文本对话的点赞率”，“对话模式”和“对话类型”两个维度都包含“文本对话”的维度取值。用结构化问题中 `scope` 里每一项数据维度去和 Step 2.2 探查到的表元数据中的可用维度对比。若**多列**都能取到该值（如“文本对话”既在 `dialog_mode` 也在 `dialog_type` 里出现）→ 有歧义。
- 若存在歧义，则抛出问题以及待确定的选项（即，所有可用维度），让用户确认。

> 上述只是最常见的情况，遇到其他模糊（如时间口径——“近 30 天”含不含今天、“上月”按自然月还是滚动 30 天；如统计口径——“用户数”是 `visit_usercnt` 还是 `visit_login_usercnt`）也要在本步骤一并提出。

#### Step 3.3. 消歧结论

无论是否触发消歧，都要在**回复正文**中说明以下内容：

- **检查结果**
  - 指标歧义：<无 | 逐条列出每个模糊指标命中的候选清单>
  - 粒度歧义：<无 | 按默认（写明默认值与场景，如“统计值=日均”“环比基础数据=日均均值”“基于用户数的比例=每日比例的日均值”“基于次数/时长的比例=时段求和后相除”）| 逐条列出每个模糊指标命中的候选 `metric_name` / formula>
  - 维度歧义：<无 | 逐条列出每个模糊维度命中的候选清单>
  - 数据表来源：<单一可用表，无需选择 | 候选表清单 + 最终选定表（组合）+ 选定理由（满足需求 / 描述更贴合 / 最准确计算指标·非代理指标 / 概览表兜底）>
  - 其他：<无 | 描述时间口径 / 统计口径等其他模糊>
- **用户确认 / 默认**：<原模糊项> → <最终选择>（例：`留存率` → `访问留存率`；`对话用户数` → `日均对话用户数`（按默认）；`文本对话` → `dialog_mode=文本对话`）
- **对结构化问题的修改**：<字段路径> 从 <旧值> 改为 <新值>；若无修改写“无”

> **联动**：出现下列任一情况，都意味着 Step 2 的指标信息 / 表元数据可能缺失对应元数据 → **回到 Step 2** 重新拉，再进入 Step 4：
> - 消歧后写回的 `metric_name` **未出现**在 Step 2.1 现有产物中（无论是改名、新增还是切换聚合粒度）；
> - 消歧后选定的数据表（组合）**未出现**在 Step 2.2 现有产物中，或其在 Step 2.1 指标 `formulas` 里缺少对应 `dataset` 的计算口径（换表 / 新增表场景）。
>
> 若写回的 `metric_name` 已存在于 Step 2.1 现有产物中（包括 1.2 聚合粒度消歧切到同一指标的另一个聚合粒度版本），且选定数据表的元数据与指标口径均已齐备，则无需重跑 Step 2。

### Step 4. NL2SQL Prompt 生成

使用 `{skill-dir}/scripts/generate_nl2sql_prompt.py` 将 Step 2 探查到的指标信息和表元数据信息渲染成 NL2SQL prompt，落盘到 `{workdir}/steps/`：

```bash

python {skill-dir}/scripts/generate_nl2sql_prompt.py \
  --problem <{workdir}/steps/ 下 Step 1 结构化问题产物路径> \
  --metrics <{workdir}/data/raw/ 下 Step 2.1 指标信息产物路径> \
  --schema  <{workdir}/data/raw/ 下 Step 2.2 表元数据产物路径> \
  --output  <{workdir}/steps/ 下的输出路径>
```

**参数说明**

| 参数 | 含义 | 默认值 |
|------|------|------|
| `--problem` | 问题结构化 JSON 路径（Step 1 的产物，位于 `{workdir}/steps/`；Step 3 可能就地改写过） | 必填 |
| `--metrics` | 关键分析指标信息 JSON 路径（Step 2.1 的产物，位于 `{workdir}/data/raw/`） | 必填 |
| `--schema` | 相关数据表元数据信息 JSON 路径（Step 2.2 的产物，位于 `{workdir}/data/raw/`） | 必填 |
| `--output` | NL2SQL prompt 输出路径（须位于 `{workdir}/steps/` 下） | 必填 |

- 产出的 NL2SQL prompt 供 Step 5 整段交给 LLM。

### Step 5: SQL 生成

1. 读取 Step 4 在 `{workdir}/steps/` 下产出的 NL2SQL prompt 的**完整内容**，**不允许**对其内容做更改。
2. 把它**整段**作为 prompt 交给当前模型（不要节选、不要总结、不要在前面加额外指令）。
3. 查看 [`references/partition-query.md`](references/partition-query.md) 中的分区与查询策略，根据表后缀确定分区字段和 WHERE 写法，结合表类型确定查询范围。
4. 模型必须**只输出一条可执行 `SELECT`**。
5. 从模型回复中只截取 SQL 主体（去掉 ```sql``` 代码围栏、Markdown 注释、解释段），保存到 `{workdir}/steps/` 下（以 `;` 结尾）。
6. 不要二次改写 SQL；如果模型输出多条语句，回到 Step 4 检查 prompt，**不要**手工拼接。

### Step 6: 执行 SQL 并返回结果

通过**数据湖仓**执行 Step 5 产出的 SQL。执行结果按下述规则处理：

1. **执行成功，拿到非空行** → 返回结果，保存至本地 CSV 文件；若返回结果被截断，则通过下载数据文件的方式保存到本地。
2. **执行成功，但返回空结果集** → **不要假装成功**。按下列顺序排查：
   - 回到 Step 1，检查 `scope`（特别是日期）/ `metrics` 是否对齐；
   - 回到 Step 3，检查是否漏过歧义——例如指标 / 维度选错列；
   - 回到 Step 5，检查 SQL 的过滤条件；
   - 之后重跑 Step 2–6（若 Step 3 改写了 metrics 名单则必须重跑 Step 2，否则可从 Step 4 起跑）。
3. **执行错误，错误返回** → 把 `error` 信息透传回去做迭代：SQL 语法 / 列不存在 / 类型不匹配等 → 回到 Step 5；表 / 列名查不到 → 回到 Step 2 重新拉表元数据。**不要**绕过数据湖仓的标准执行通道（如改走 shell / `psql` 自行取数），那样拿到的结果既不可复现也无法被评测器审计。

