当数据库表和字段逐渐增多,仅靠记忆编写 SQL 往往会拖慢查询效率。Text2SQL 提供了另一种交互方式:用户描述所需数据,大模型结合数据库 Schema 生成对应语句。下面将使用 DeepSeek、Python 和 SQLite 搭建一条完整链路,并拆解提示词设计、SQL 执行与多表关联中的关键环节。
让 AI 帮你写 SQL,告别繁琐的表结构记忆和语法调试
在日常开发中,我们经常需要与数据库打交道。写 SQL 查询本身并不复杂,但当表变多、字段变复杂时,记字段名、写 JOIN、调条件总是让人分心。Text2SQL 技术应运而生——你只需要用自然语言描述“我要什么”,AI 直接帮你生成 SQL 语句。本文将带你从零开始,用 Python + SQLite + DeepSeek 大模型搭建一个完整的 Text2SQL 系统,并深度剖析每个底层细节。
我们先看一眼最终实现的效果:
graph LR
A[自然语言问题] --> B[DeepSeek 大模型]
B --> C[生成 SQL 语句]
C --> D[SQLite 执行]
D --> E[返回查询结果]
用户问:“工程部门员工的姓名和工资是多少?”
系统输出:SELECT name, salary FROM EMPLOYEES WHERE department = '工程';
然后自动执行并打印结果。整个过程无需手写一行 SQL。
首先,我们需要调用 DeepSeek 的 API。DeepSeek 提供了与 OpenAI 完全兼容的接口,所以直接用 openai 库即可。
from openai import OpenAI
client = OpenAI(
api_key="sk-dbd5d0d816254a66825aa2c0841a35b2",
base_url="https://api.deepseek.com/v1"
)
? 知识点拆解:
https://api.openai.com/v1。这相当于告诉客户端“我换了个后端”。我们选用 SQLite 作为演示数据库,因为它无需安装、无需配置、单文件存储,非常适合本地开发和教学。
import sqlite3
conn = sqlite3.connect("test.db") # 创建/连接数据库文件
cursor = conn.cursor() # 创建游标对象
? 游标(Cursor)是什么?
游标是数据库操作中的“指针”,它代表一个执行 SQL 语句的上下文环境。你可以通过游标来执行 SQL、获取结果。一个连接可以创建多个游标,每个游标独立管理自己的执行状态。底层实现中,游标会维护一个结果集和当前读取位置。
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
department TEXT,
salary INTEGER
)
""")
sample_data = [
(6, "张三", "销售", 5000),
(7, "李四", "市场", 6000),
(8, "王五", "研发", 7000),
(9, "赵六", "工程", 8000),
(10, "王二", "市场", 9000),
]
cursor.executemany(
"INSERT INTO employees VALUES (?,?,?,?)",
sample_data
)
conn.commit()
? 重点知识点:
IF NOT EXISTS:避免重复执行脚本时表已存在报错,保证幂等性。AUTOINCREMENT:在 SQLite 中,INTEGER PRIMARY KEY 默认就会自增,但加上 AUTOINCREMENT 可以保证自增的值永不重用(更安全,但稍微慢一点)。executemany:批量插入的利器。它接受一个 SQL 模板(用 ? 作为占位符)和一个元组列表,内部会一次性编译 SQL 并逐行绑定参数。相比循环执行 execute,这能大幅减少 SQL 解析次数,提升性能。?:SQLite 使用 ? 作为参数占位符(位置绑定),而不是 %s 或 :name。这可以有效防止 SQL 注入,因为参数值是独立于 SQL 语句传输的,不会被当作代码执行。commit():executemany 默认不会自动提交,必须显式调用 conn.commit() 将更改持久化到磁盘。这是事务控制的基础——要么全部成功,要么全部回滚。你可以用 conn.rollback() 撤销未提交的修改。要让 AI 生成正确的 SQL,必须告诉它数据库中有哪些表、每个表的字段名和类型。这就是 Schema(模式)的作用。
schema = cursor.execute("PRAGMA table_info(employees)").fetchall()
print(schema)
# 输出: [(0, 'id', 'INTEGER', 0, None, 1), (1, 'name', 'TEXT', 0, None, 0), ...]
? PRAGMA table_info 是什么?
PRAGMA 是 SQLite 特有的“编译指令”或“元数据查询命令”。table_info(table_name) 返回一个结果集,每一行代表表中的一个列,包含:
我们用 fetchall() 取出所有列信息,然后拼接成建表语句的字符串形式:
schema_str = "CREATE TABLE EMPLOYEES (n" + "n".join([f"{col[1]} {col[2]}" for col in schema]) + "n)"
? 列表推导式与 f-string:
[f"{col[1]} {col[2]}" for col in schema] 遍历每个列,取出 name 和 type,拼接成类似 "id INTEGER" 的字符串,再用换行符连接。这样我们就得到了一个可读的 Schema 描述,供 AI 理解。
def ask_deepseek(query, schema):
prompt = f"""
这是一个数据库的Schema:
{schema}
根据这个Schema, 请输出一个SQL查询来回答以下问题。
只输出SQL 查询语句本身,不要使用任何Markdown格式,
不要包含反引号、代码块标记或额外说明。
问题:{query}
"""
response = client.chat.completions.create(
model="deepseek-v4-flash",
max_tokens=2048,
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
? 提示词工程(Prompt Engineering)的关键点:
execute。deepseek-v4-flash(实际可用 deepseek-chat 等),DeepSeek 在代码生成和 SQL 任务上表现优异。max_tokens:限制输出长度,防止过长回复。? 底层调用过程:
client.chat.completions.create 发送 HTTP 请求到 DeepSeek 服务端,服务端运行大模型推理,返回一个 JSON 响应。我们从中提取 choices[0].message.content 即生成的 SQL。
question = "工程部门员工的姓名和工资是多少?"
sql_query = ask_deepseek(question, schema_str)
print(sql_query) # SELECT name, salary FROM EMPLOYEES WHERE department = '工程';
results = cursor.execute(sql_query).fetchall()
for row in results:
print(row) # ('赵六', 8000)
AI 自动识别了“工程部门”对应字段 department,并只选择了 name 和 salary 两列,完美!
question = "在销售部门增加一个新员工,姓名为张三,工资为45000"
sql_query = ask_deepseek(question, schema_str)
print(sql_query) # INSERT INTO EMPLOYEES (name, department, salary) VALUES ('张三', '销售', 45000);
cursor.execute(sql_query)
conn.commit()
注意:这里 AI 没有指定 id,因为 id 是自增主键,数据库会自动分配。它甚至用了完整的列名列表,这是最佳实践。
question = "将王二的工资调整为55000"
sql_query = ask_deepseek(question, schema_str)
print(sql_query) # UPDATE EMPLOYEES SET salary = 55000 WHERE name = '王二';
cursor.execute(sql_query)
conn.commit()
AI 直接根据“王二”这个姓名作为条件,非常合理(假设姓名唯一)。
question = "删除市场部门的王二"
sql_query = ask_deepseek(question, schema_str)
print(sql_query) # DELETE FROM EMPLOYEES WHERE department='市场' AND name='王二';
cursor.execute(sql_query)
conn.commit()
这里 AI 加上了 department 条件以避免误删同名员工,安全考虑很到位。
很多业务场景不止一张表。我们新增一个 departments 表,存储部门经理信息。
cursor.execute("""
CREATE TABLE IF NOT EXISTS departments(
id INTEGER PRIMARY KEY,
name TEXT,
manager TEXT
)
""")
sample_departments = [
(1, "销售", "王经理"),
(2, "工程", "李经理"),
(3, "市场", "张经理")
]
cursor.executemany("INSERT INTO departments VALUES (?,?,?)", sample_departments)
conn.commit()
此时数据库有两张表:employees 和 departments。我们需要把完整的 Schema 重新拼接给 AI。
tables = ["employees", "departments"]
schema_str = ""
for table in tables:
schema = cursor.execute(f"PRAGMA table_info({table})").fetchall()
schema_str += f"CREATE TABLE {table} (n" + "n".join([f"{col[1]} {col[2]}" for col in schema]) + "n);nn"
现在,我们问一个涉及两张表的问题:
question = "根据两个表之间的关系,列出每个部门的员工人数和平均工资"
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
-- 输出:
-- SELECT d.name, COUNT(e.id) AS employee_count, AVG(e.salary) AS avg_salary
-- FROM departments d LEFT JOIN employees e ON d.name = e.department
-- GROUP BY d.id, d.name;
? AI 如何发现“关系”?
它通过字段名相似性(departments.name 与 employees.department)推断出两张表可以通过部门名称关联。虽然我们没有显式定义外键,但 AI 利用语义理解完成了 JOIN 条件的推断。这就是大模型的强大之处。
? LEFT JOIN 与 GROUP BY:
LEFT JOIN 保证没有员工的部门也会出现(计数为 0)。GROUP BY d.id, d.name 按部门分组,然后计算每组的员工数(COUNT(e.id))和平均工资(AVG(e.salary))。通过本文,我们完整实现了一个 Text2SQL 系统,核心收益如下:
| 传统方式 | Text2SQL 方式 |
|---|---|
| 需要记住所有表名、字段名 | 只需用业务语言描述需求 |
| 需要掌握 SQL 语法细节 | AI 自动生成符合语法的 SQL |
| 多表关联容易出错 | AI 推断关联关系 |
| 调试 SQL 耗时 | 生成即正确(大部分情况下) |
? 关键知识点回顾:
executemany:批量绑定性插入,提升性能并防止注入。commit():确保数据持久化,可回滚。PRAGMA table_info:获取表结构的元数据命令。? 延伸思考:
Text2SQL 正在改变我们与数据交互的方式——它让数据查询不再是程序员的专利,产品、运营同学也能用自然语言自由探索数据。而这一切,得益于大模型对代码和结构化数据的深刻理解。
希望本文能帮你快速上手 Text2SQL,并在自己的项目中发挥创造力。动手试试吧,你的下一个应用可能就是“对话式 BI”工具!
tplink ac路由器可以用其他poe吗(tplink ac路由器支持其他poe吗)
tplink ac路由器可以管理家用路由器吗(tplink ac路由器有管理家用路由器功能吗)
GPT-5.6 Sol 在 Codex 中的 token 消耗为何可能高于 GPT-5.5
Kimi K3 是否具备长程编码和 Agent 任务能力?
Kimi K3 生成编程项目时为什么耗时严重?
tplink路由器wdr6300参数配置(tplink路由器wdr6300参数配置方法)