教程 - 构建 SQL 助手工作流

工作流概览
本教程展示了如何创建一个 SQL 助手工作流,该工作流支持使用自然语言查询 SQL 数据库。市场营销人员和产品经理等非技术用户可以使用此工具独立查询业务数据,减少对数据分析师的依赖。它还可以作为学校和编程课程中 SQL 的教学辅助工具。完成的工作流操作如下
数据库结构、字段描述和 SQL 示例作为知识库存储在 RAGFlow 中。当用户查询时,系统会从这些来源检索相关信息,并将其传递给一个 Agent,由该 Agent 生成 SQL 语句。然后,这些语句由一个 SQL 执行器组件执行,以返回查询结果。
操作步骤
1. 创建三个知识库
1.1 准备数据集文件
您可以从 Hugging Face Datasets 下载示例数据集。

以下是预定义的示例文件
- Schema.txt
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(50) NOT NULL,
`email` VARCHAR(100),
`mobile` VARCHAR(20),
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_email` (`email`),
UNIQUE KEY `uk_mobile` (`mobile`)
);
...
注意:在定义 schema 字段时,避免使用下划线等特殊字符,因为它们可能导致 LLM 生成的 SQL 语句出错。 2. Question to SQL.csv
What are the names of all the Cities in Canada
SELECT geo_name, id FROM data_commons_public_data.cybersyn.geo_index WHERE iso_name ilike '%can%
What is average Fertility Rate measure of Canada in 2002 ?
SELECT variable_name, avg(value) as average_fertility_rate FROM data_commons_public_data.cybersyn.timeseries WHERE variable_name = 'Fertility Rate' and geo_id = 'country/CAN' and date >= '2002-01-01' and date < '2003-01-01' GROUP BY 1;
What 5 countries have the highest life expectancy ?
SELECT geo_name, value FROM data_commons_public_data.cybersyn.timeseries join data_commons_public_data.cybersyn.geo_index ON timeseries.geo_id = geo_index.id WHERE variable_name = 'Life Expectancy' and date = '2020-01-01' ORDER BY value desc limit 5;
...
- Database Description EN.txt
### Users Table (users)
The users table stores user information for the website or application. Below are the definitions of each column in this table:
- `id`: INTEGER, an auto-incrementing field that uniquely identifies each user (primary key). It automatically increases with every new user added, guaranteeing a distinct ID for every user.
- `username`: VARCHAR, stores the user’s login name; this value is typically the unique identifier used during authentication.
- `password`: VARCHAR, holds the user’s password; for security, the value must be encrypted (hashed) before persistence.
- `email`: VARCHAR, stores the user’s e-mail address; it can serve as an alternate login credential and is used for notifications or password-reset flows.
- `mobile`: VARCHAR, stores the user’s mobile phone number; it can be used for login, receiving SMS notifications, or identity verification.
- `create_time`: TIMESTAMP, records the timestamp when the user account was created; defaults to the current timestamp.
- `update_time`: TIMESTAMP, records the timestamp of the last update to the user’s information; automatically refreshed to the current timestamp on every update.
...
1.2 在 RAGFlow 中创建知识库
Schema 知识库
创建一个名为“Schema”的知识库,并上传 Schema.txt 文件。

数据库中的表长度各不相同,每个表都以分号(;)结尾。
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`password` VARCHAR(50) NOT NULL,
...
UNIQUE KEY `uk_mobile` (`mobile`)
);
CREATE TABLE `products` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`price` DECIMAL(10, 2) NOT NULL,
`stock` INT NOT NULL,
...
FOREIGN KEY (`merchant_id`) REFERENCES `merchants` (`id`)
);
CREATE TABLE `merchants` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`email` VARCHAR(100),
...
UNIQUE KEY `uk_mobile` (`mobile`)
);
为了将每个表隔离为独立的块且内容不重叠,按如下方式配置知识库参数
- 分块方法:通用
- 块大小:2 个 token(隔离的最小尺寸)
- 分隔符:分号 (;) RAGFlow 将根据此工作流解析并生成块

以下是 Schema.txt 解析结果的预览

我们现在通过检索测试来验证检索到的结果

Question to SQL 知识库
创建一个名为“Question to SQL”的新知识库,并上传“Question to SQL.csv”文件。

将分块方法设置为 Q&A,然后解析 Question_to_SQL.csv 以预览结果。

我们现在通过检索测试来验证检索到的结果

数据库描述知识库 创建一个名为“Database Description”的新知识库,并上传“Database_Description_EN.txt”文件。

配置(与 Schema 知识库相同)
- 分块方法:通用
- 块大小:2 个 token(隔离的最小尺寸)
- 分隔符:分号 `###` 以下是配置后解析 Database_Description_EN.txt 的预览。

我们现在通过检索测试来验证检索到的结果

注意:这三个知识库是分开维护和查询的。Agent 组件在生成输出前会整合所有来源的结果。

2. 编排工作流
2.1 创建工作流应用
成功创建后,开始组件会自动出现在画布上。

您可以在开始组件中配置欢迎消息。例如
Hi! I'm your SQL assistant, what can I do for you?
2.2 配置三个检索(Retrieval)组件
在开始组件后添加三个并行的检索组件,命名如下
- Schema
- Question to SQL
- Database Description 配置每个检索组件
- 查询变量:sys.query
- 知识库选择:选择名称与当前组件名称匹配的知识库。

2.3 配置 Agent 组件
在检索组件后添加一个名为“SQL 生成器”的 Agent 组件,并将所有三个检索组件连接到它。

编写系统提示
### ROLE
You are a Text-to-SQL assistant.
Given a relational database schema and a natural-language request, you must produce a **single, syntactically-correct MySQL query** that answers the request.
Return **nothing except the SQL statement itself**—no code fences, no commentary, no explanations, no comments, no trailing semicolon if not required.
### EXAMPLES
-- Example 1
User: List every product name and its unit price.
SQL:
SELECT name, unit_price FROM Products;
-- Example 2
User: Show the names and emails of customers who placed orders in January 2025.
SQL:
SELECT DISTINCT c.name, c.email
FROM Customers c
JOIN Orders o ON o.customer_id = c.id
WHERE o.order_date BETWEEN '2025-01-01' AND '2025-01-31';
-- Example 3
User: How many orders have a status of "Completed" for each month in 2024?
SQL:
SELECT DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS completed_orders
FROM Orders
WHERE status = 'Completed'
AND YEAR(order_date) = 2024
GROUP BY month
ORDER BY month;
-- Example 4
User: Which products generated at least \$10 000 in total revenue?
SQL:
SELECT p.id, p.name, SUM(oi.quantity * oi.unit_price) AS revenue
FROM Products p
JOIN OrderItems oi ON oi.product_id = p.id
GROUP BY p.id, p.name
HAVING revenue >= 10000
ORDER BY revenue DESC;
### OUTPUT GUIDELINES
1. Think through the schema and the request.
2. Write **only** the final MySQL query.
3. Do **not** wrap the query in back-ticks or markdown fences.
4. Do **not** add explanations, comments, or additional text—just the SQL.

编写用户提示
User's query: /(Begin Input) sys.query
Schema: /(Schema) formalized_content
Samples about question to SQL: /(Question to SQL) formalized_content
Description about meanings of tables and files: /(Database Description) formalized_content
插入变量后,填充的结果如下所示

2.4 配置 ExeSQL 组件
在 SQL 生成器后追加一个名为“SQL 执行器”的 ExeSQL 组件。

为 SQL 执行器组件配置数据库,指定其查询输入来自 SQL 生成器的输出。

2.5 配置消息(Message)组件
在 SQL 执行器后追加一个消息组件。
在“消息”字段中插入变量,使消息组件能够显示 SQL 执行器的输出(formalized_content)

2.6 保存并测试
点击保存 → 运行 → 输入一个自然语言问题 → 查看执行结果。


结语
最后,与目前的 Copilot 技术一样,NL2SQL 无法实现完全准确。对于结构化数据的标准化处理,我们建议将其操作整合到特定的 API 中,然后将这些 API 封装为 RAGFlow 的 MCP(托管内容包)。我们将在即将发布的博客中演示这种方法。