前言
你有没有想过这样一个场景:老板突然发消息问“上个月销售部门平均工资是多少”,你不想打开数据库客户端,不想手写 SQL,只想一句话丢给 AI,它就能给你答案。
这就是 Text2SQL——把自然语言翻译成 SQL 语句。听起来高大上,但说白了就是:把数据库的 Schema 告诉大模型,让它根据自然语言生成对应 SQL。
今天就带你用最简单的 SQLite + DeepSeek 跑通一个完整 Demo,从零到一实现「自然语言查数据库」。看完你就能直接用到自己的小项目里。
一、为什么选 SQLite 做 Text2SQL 的底座?
做 Text2SQL,数据库选型很关键。很多人第一反应是 MySQL、PostgreSQL,但其实 SQLite 才是最佳练手场。
原因很简单:
- 零配置:不用装服务、不用配账号密码,一个文件就是一个数据库
- 系统内置:Python 标准库
sqlite3直接就能用,不需要额外依赖 - Schema 简洁:用
PRAGMA table_info一条命令就能拿到完整的表结构,喂给大模型特别方便
一句话总结:SQLite 是「文件型的关系型数据库」,也是学习 Text2SQL 的最短路径。
二、先把地基打牢:建表 + 插入数据
我们先用一个员工表 employees 做例子,字段有:id、姓名、部门、工资。
import sqlite3
# 1. 连接数据库(文件不存在会自动创建)
conn = sqlite3.connect("test.db")
cursor = conn.cursor()
# 2. 建表
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT,
salary INTEGER
)
""")
# 3. 批量插入数据(注意:executemany 用的是「参数占位符」而不是拼接字符串)
sample_data = [
(6, "黄佳", "销售", 50000),
(7, "宁宁", "工程", 75000),
(8, "谦谦", "销售", 60000),
(9, "悦悦", "工程", 80000),
(10, "黄仁勋", "市场", 55000),
]
cursor.executemany(
"INSERT INTO employees VALUES (?, ?, ?, ?)",
sample_data
)
conn.commit()
⚠️ 踩坑提醒:批量插入一定要用 executemany + ? 占位符,别用 f-string 拼 SQL。前者性能好、还能防 SQL 注入;后者不仅慢,还容易出安全事故。
另外记住一件事:SQLite 默认是手动事务的,写完数据一定要 conn.commit() ,否则换一个连接就读不到了。
三、把 Schema 送给大模型:这是 Text2SQL 的关键
Text2SQL 的核心思路就一句话:大模型不知道你的表长什么样,你得把 Schema 告诉它。
SQLite 提供了一个非常方便的命令来获取表结构:
schema = cursor.execute("PRAGMA table_info(employees)").fetchall()
print(schema)
返回的数据大概是这样:
[(0, 'id', 'INTEGER', 0, None, 1),
(1, 'name', 'TEXT', 0, None, 0),
(2, 'department', 'TEXT', 0, None, 0),
(3, 'salary', 'INTEGER', 0, None, 0)]
每一行代表一列,其中 col[1] 是列名,col[2] 是类型。我们把它拼成一段「CREATE TABLE 语句」风格的字符串,大模型一看就懂:
schema_str = "CREATE TABLE EMPLOYEES (\n" + \
"\n".join([f"{col[1]} {col[2]}" for col in schema]) + "\n)"
print(schema_str)
输出:
CREATE TABLE EMPLOYEES (
id INTEGER
name TEXT
department TEXT
salary INTEGER
)
小技巧:Schema 描述越接近真实 DDL(建表语句),大模型生成的 SQL 越准。因为它训练时见过太多这种格式了。
四、让大模型把「人话」翻译成 SQL
接下来就是最激动人心的一步。我们封装一个函数,把「用户问题 + Schema」一起丢给 DeepSeek:
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=2024,
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
这里有几个Prompt 工程的关键细节,新手特别容易忽略:
- 明确输出格式:一定要强调「只输出 SQL,不要 Markdown」。否则大模型很爱给你包成
sql ...,直接执行会报错。 - Schema 放在问题前面:这样模型可以先「读题」再「作答」,生成更稳定。
- 越简单的 prompt 越容易复现:不要加太多花哨的约束,先跑通再优化。
五、跑起来:自然语言查数据
场景 1:查询
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)
场景 2:插入
question = "在销售部门增加一个新员工,姓名为张三,工资为45000"
sql_query = ask_deepseek(question, schema_str)
# INSERT INTO EMPLOYEES (name, department, salary) VALUES ('张三', '销售', 45000)
cursor.execute(sql_query)
conn.commit()
场景 3:更新 / 删除
question = "将黄佳的工资调整为550000"
sql_query = ask_deepseek(question, schema_str)
# UPDATE EMPLOYEES SET salary = 550000 WHERE name = '黄佳'
cursor.execute(sql_query)
conn.commit()
question = "删除部门为市场的黄仁勋"
sql_query = ask_deepseek(question, schema_str)
# DELETE FROM EMPLOYEES WHERE name = '黄仁勋' AND department = '市场'
cursor.execute(sql_query)
conn.commit()
你会发现,增删改查全都能用自然语言搞定。这就是 Text2SQL 的威力——它把「数据库」这件事从专业门槛降低到了「会说话就行」。
💡 金句时间:ChatGPT 平权了写代码,Text2SQL 平权了查数据库。
六、进阶:多表场景怎么搞?
真实业务里不可能只有一张表。我们再加一张 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()
这时候光把单表 Schema 喂给模型就不够了,我们需要把所有相关表的 Schema 拼在一起:
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);\n\n"
print(schema_str)
输出:
CREATE TABLE employees (
id INTEGER
name TEXT
department TEXT
salary INTEGER
);
CREATE TABLE departments (
id INTEGER PRIMARY KEY
name TEXT
manager TEXT
);
然后丢一个问题试试:
question = "根据两个表之间的关系,列出每个部门的员工人数和平均工资"
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
你大概率会拿到类似这样的 SQL:
SELECT
d.name AS department,
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.name
注意这里的三个关键点:
- 关联字段靠命名推断:
employees.department和departments.name名字对上了,模型才能猜出关联关系。这就是为什么「字段命名规范」在 Text2SQL 场景里极其重要。 - LEFT JOIN vs INNER JOIN:模型选了 LEFT JOIN,保证即使某部门没有员工也能列出来,这就是「业务语义」比 SQL 语法更难的地方。
- COUNT + AVG + GROUP BY:聚合 + 分组的经典组合,模型一次到位。
七、踩坑清单:这些细节不注意会翻车
实践下来,我总结了几个「不做就等着翻车」的点:
| 坑点 | 解决方案 | ||
|---|---|---|---|
| 模型返回带 ```sql 代码块 | Prompt 明确要求「只输出 SQL,不要 Markdown」 | ||
| 增删改操作没生效 | 记得 conn.commit(),SQLite 默认不是自动提交 | ||
| 多表查询关联不上 | 表字段命名要对齐(比如员工表用 department,部门表用 name) | ||
| 生成的 SQL 有语法错误 | 执行前先 try/except 包一层,错误信息也能回喂给模型自我修复 | ||
| 中文语义歧义 | 比如“平均工资”要明确是「人均」还是「平均到每个月」,Prompt 里最好补一句业务背景 |
八、总结:Text2SQL 的核心公式
如果只能记一句话,那就是:
Text2SQL = Schema 描述 + 精准 Prompt + 结果执行
拆开来看:
- Schema 是地基:字段名、类型、表关联,决定模型能不能「看懂」你的数据
- Prompt 是油门:输出格式、约束条件,决定模型答得稳不稳
- 执行 + 提交是刹车:
execute前想清楚是不是写操作,commit前想清楚是不是真的改
从这段 200 行的 Demo 出发,你可以继续往下做很多事:
- 加个 CLI 或 Web UI,做成真正能用的工具
- 加个「SQL 审计」环节,只允许 SELECT,防误删
- 加个「多轮对话」,让用户能追问(“那工程部呢?”)
- 换成其他 LLM 对比效果,或者接本地的 Ollama 做私有化
技术在飞,但方向很清晰:未来的软件,会说话的人就能操作数据。
你准备好把这句话变成现实了吗?🚀