在传统的软件开发中,如果我们想查数据库,往往要自己写 SQL:
SELECT name, salary FROM employees WHERE department = '开发部';
对业务同学、产品同学来说,这门槛不低。
在 AIGC(AI Generated Content)时代,我们可以换一种思路:
“帮我查一下开发部所有员工的姓名和工资。”
然后,把这句话交给大模型,让它自动生成 SQL 并执行。
这就是 Text-to-SQL(自然语言生成 SQL) 的典型场景。
本文会用一个完整但很小的例子,带你从零开始实现:
- 在本地用轻量级关系型数据库存数据
- 提取数据库结构(Schema)
- 调用大模型,根据自然语言自动生成 SQL
- 最终形成一个“自然语言 → SQL → 查询结果”的闭环
一、从 CRUD 到自然语言:我们要解决什么问题?
先回到最传统的场景:数据库操作的核心是 CRUD:
- C:Create(增)
- R:Read(查)
- U:Update(改)
- D:Delete(删)
这套模式几十年没变,问题在于:
- 需要掌握 SQL 语法
- 经常被细节问题绊住(字段名拼错、大小写、类型不匹配等)
而在 AIGC 的帮助下,我们可以把这件事情拆成两部分:
- 人:用自然语言描述需求
- 模型:理解需求 + 结合数据库结构 → 生成合法 SQL
也就是说,人不再必须是 “会写 SQL 的操作者”,而是 “提出需求的提问者”。
二、打地基:轻量级关系型数据库 + Python
要做 Text-to-SQL,第一步仍然是要有一个可以操作的数据库。
这里选择的是一个非常轻量级的关系型数据库,它有几个优点:
- 不需要单独安装数据库服务
- 一个本地文件就是一个数据库
- 非常适合 Demo、教学、原型验证
在 Python 中,只需要:
import sqlite3
# 1. 连接数据库(如果文件不存在会自动创建)
conn = sqlite3.connect("test.db")
# 2. 获取“游标”(cursor),相当于操作数据库的句柄
cursor = conn.cursor()
有了连接和游标,我们就可以开始建表、插入数据。
1. 建表:定义一个“员工表”
我们用一个典型的“员工表”来作为示例数据模型:
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT,
salary INTEGER
)
""")
这个表包含四个字段:
id:主键name:姓名department:部门salary:工资
这已经足够支撑很多常见的业务问题,例如:
- “开发部有哪些人?”
- “销售部的平均工资是多少?”
- “工资最高的是谁?”
2. 插入一些示例数据
接着,我们往表里放入几条记录:
sample_data = [
(6, "陈h", "开发部", 32000),
(7, "张三", "销售部", 20000),
(8, "曹ww", "开发部", 33000),
(9, "李四", "销售部", 15000)
]
cursor.executemany(
"INSERT INTO employees VALUES (?, ?, ?, ?)",
sample_data
)
conn.commit()
现在,我们已经有了一个最小可用的“公司员工数据库”。
三、让数据库“自我介绍”:提取 Schema
要让大模型写 SQL,它必须先知道:
- 数据库里有哪些表?
- 每张表有哪些字段?字段类型是什么?
这部分信息,就叫做 Schema(模式/结构) 。
在这个例子里,我们使用数据库内置命令获取表结构:
schema_info = cursor.execute(
"PRAGMA table_info(employees)"
).fetchall()
schema_str = "CREATE TABLE EMPLOYEES (\n" + \
"\n".join([f"{col[1]} {col[2]}" for col in schema_info]) + \
"\n)"
print("数据库 Schema:")
print(schema_str)
打印出来大致类似:
数据库 Schema:
CREATE TABLE EMPLOYEES (
id INTEGER
name TEXT
department TEXT
salary INTEGER
)
这里有几个关键点:
- 把结构整理成接近 SQL 的描述字符串,便于大模型理解
- 统一使用大写表名
EMPLOYEES,避免大小写混乱 - 这份 Schema 会作为 Prompt 的一部分喂给大模型
可以把这一步理解为:
在让大模型写 SQL 之前,先让数据库做一个“自我介绍”。
四、接入大模型:设计一个专门生成 SQL 的 Prompt
接下来,就是把自然语言问题和 Schema 一起交给大模型,让它产出 SQL。
这里使用的是兼容 OpenAI 接口的大模型 Python SDK,大致初始化如下(注意:实际项目中请用环境变量管理密钥):
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.deepseek.com/v1"
)
真正的“灵魂”在于 Prompt 的设计。我们封装了一个函数,专门用于“问大模型要 SQL”:
def generate_sql_from_nl(query, schema):
prompt = f"""
这是一个数据库的 Schema:
{schema}
根据这个 Schema, 你能输出一个 SQL 查询来回答以下问题吗?
只输出 SQL 查询, 不要输出任何其他内容, 也不要带任何格式。
问题:{query}
"""
response = client.chat.completions.create(
model="deepseek-reasoner",
max_tokens=2048,
messages=[{
"role": "user",
"content": prompt
}]
)
return response.choices[0].message
这里有三条非常重要的 Prompt 约束:
- 给足上下文:把 Schema 原样给到模型
- 明确任务:根据 Schema 生成 SQL 来回答问题
- 强约束输出格式:只允许输出 SQL,不要自然语言解释、不要代码块标记
模型选择的是一个带“推理能力”的版本,这对于理解结构、推导正确字段名非常关键。
五、跑通闭环:从中文问题到 SQL,再到查询结果
现在,我们可以试着用一句自然语言提问,例如:
question = "开发部员工的姓名和工资是多少?"
message = generate_sql_from_nl(question, schema_str)
print("模型生成的 SQL:")
print(message.content)
典型的输出可能是:
SELECT name, salary FROM EMPLOYEES WHERE department = '开发部';
到这里,我们已经成功把“自然语言问题”翻译成了“SQL 查询”。
若再加上执行这条 SQL 的代码,例如:
generated_sql = message.content.strip()
cursor.execute(generated_sql)
rows = cursor.fetchall()
for row in rows:
print(row)
就可以完整实现:
自然语言提问 → 大模型生成 SQL → 数据库执行 → 输出结果。
这就是最小可用的 Text-to-SQL 系统。
六、为什么要用“推理型”大模型?
在实践中会遇到一些有趣的问题,比如:
- 字段名拼写问题(
department不小心写成了别的) - 表名、字段名大小写不统一
- 问题本身比较模糊,需要推理才能确定查询逻辑
普通的“补全文本”模型,往往只是根据语料习惯“猜”出一段看起来像 SQL 的东西;
而带推理能力的模型,会:
- 先认真阅读 Schema
- 再分析问题意图
- 最后按结构拼装出一条更合理、更稳健的 SQL
很多 API 还会在内部保留一段“推理内容”,帮助你理解模型是怎么想的,这对于调试 Prompt 和改进系统行为都非常有价值。
七、从示例到实战:可以继续深入的方向
在这个小示例中,我们完成了一条从自然语言到 SQL 的完整链路。接下来可以从几个方向继续扩展:
-
扩展数据结构
- 新增更多表(部门表、项目表等)
- 测试多表关联、聚合查询、排序分页等复杂场景
-
增强 Prompt 工程
- 强制只生成
SELECT,防止误生成删除/修改类语句 - 要求模型在内部“先思考再给答案”,提高准确率
- 对中文问题引导出更结构化的中间解释
- 强制只生成
-
系统化封装
- 把这套逻辑封装成一个 API 或 Web 服务
- 前端只需要发中文问题,就能拿到查询结果
- 后端在执行 SQL 前做白名单/黑名单校验,保证安全
结语
这个小例子展示了在 AIGC 时代,数据库交互可以如何“进化”:
- 底层依然是经典的关系型数据库和 SQL
- 上层通过大模型,把“写 SQL”这件事交给了机器
- 人更多地在思考“我要问什么”而不是“SQL 怎么写”
当你习惯了这种交互方式,就会发现:
数据库不再只是一个冰冷的存储系统,而更像是一个可以对话的“数据助理”。