不写一行 SQL,用中文问一句话,数据库自动返回结果------这篇文章带你从 SQLite 基础到完整实现跑通。
前言
最近在学大模型应用开发,发现 Text2SQL 这个方向特别有意思------把自然语言翻译成 SQL 语句,让不懂技术的人也能查数据库。踩了不少坑,从 SQLite 建表到 prompt 模板设计,把整个流程走通了,整理出来分享给大家。
你将会收获:
- SQLite 基础操作:建库、建表、增删改查
- Text2SQL 的完整实现流程:从用户提问到拿到查询结果
- 两种 schema 提取方式的对比与选择
- Prompt 模板设计的核心技巧
- 面试时能讲清楚 Text2SQL 的原理和实现
技术栈: Python + SQLite + DeepSeek API(兼容 OpenAI 接口)
一、SQLite:一个文件就是一个数据库
为什么选 SQLite?
在学 Text2SQL 之前,得先搞清楚我们要查的数据库长什么样。这里用的是 SQLite------它最大的特点是零配置、单文件。
| 特性 | SQLite | MySQL / PostgreSQL |
|---|---|---|
| 安装 | 不需要,Python 内置 | 需要安装服务 |
| 配置 | 零配置 | 需要配置用户、权限、端口 |
| 存储 | 一个 .db 文件 |
服务端进程管理 |
| 适用场景 | 本地开发、教学、嵌入式 | 生产环境、高并发 |
💡 一句话记住: SQLite 就是把整个数据库塞进一个文件里,打开就能用。
建库建表:5 行代码搞定
python
import sqlite3
# 连接数据库(文件不存在会自动创建)
conn = sqlite3.connect("test.db")
cursor = conn.cursor()
# 建表
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees(
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT,
salary INTEGER
)
""")
逐行拆解:
sqlite3.connect("test.db")--- 连接到test.db文件,不存在就新建conn.cursor()--- 创建游标对象,所有 SQL 操作都通过它执行CREATE TABLE IF NOT EXISTS--- 如果表已存在就跳过,避免报错INTEGER PRIMARY KEY--- 整数主键,自动递增TEXT--- SQLite 的字符串类型
插入数据:批量插入用 executemany
python
sample_data = [
(1, "张三", "销售", 50000),
(2, "李四", "工程", 84000),
(3, "王五", "销售", 60000),
(4, "赵六", "工程", 80000),
(5, "钱七", "市场", 55000),
]
cursor.executemany("INSERT INTO employees VALUES (?,?,?,?)", sample_data)
conn.commit() # ⚠️ 不 commit 数据就丢了
⚠️ 面试考点:
execute执行单条 SQL,executemany批量插入。用?占位符而不是字符串拼接,是为了防止 SQL 注入。
commit 的作用
sql
内存中的修改 ──commit──→ 磁盘上的 .db 文件
(临时的) (持久化的)
不调用 conn.commit(),所有修改只在内存里,程序一退出就丢失。一句话:commit 是把"草稿"变成"定稿"。
二、Text2SQL:让大模型帮你写 SQL
核心思路
Text2SQL 的本质就一件事:把人话翻译成 SQL。
sql
用户提问:"工程部门员工的姓名和工资是多少?"
↓
┌─────────────────────────────────────────┐
│ Prompt = 数据库结构(schema)+ 用户问题 │
│ │
│ "这是一个数据库的 schema: │
│ CREATE TABLE employees (...); │
│ 根据这个 schema,请输出 SQL 查询..." │
└─────────────────────────────────────────┘
↓
大模型输出:SELECT name, salary FROM employees WHERE department='工程'
↓
执行 SQL → 返回结果
💡 一句话记住: Text2SQL = schema(告诉模型数据库长啥样)+ 问题(告诉模型要查啥)→ SQL。
完整代码实现
python
from openai import OpenAI
# 初始化客户端(DeepSeek 兼容 OpenAI 接口)
client = OpenAI(
api_key="你的 API Key",
base_url="https://api.deepseek.com"
)
def ask_deepseek(query, schema):
"""自然语言 → SQL"""
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
逐行拆解:
base_url="https://api.deepseek.com"--- DeepSeek 用自己的地址,但接口格式和 OpenAI 一样prompt里明确要求"只输出 SQL 本身"------防止模型输出一堆解释文字,后续没法直接执行max_tokens=2048--- 限制输出长度,简单的 SQL 查询不需要太长
串联运行:从提问到拿结果
python
import sqlite3
from test2 import ask_deepseek
# 1. 连接数据库
conn = sqlite3.connect("test.db")
cursor = conn.cursor()
# 2. 获取 schema
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
schema_str = "\n".join([table[0] for table in tables])
# 3. 提问
question = "工程部门员工的姓名和工资是多少?"
sql_query = ask_deepseek(question, schema_str)
print(f"生成的 SQL: {sql_query}")
# 4. 执行 SQL,拿结果
results = cursor.execute(sql_query).fetchall()
for row in results:
print(row)
conn.close()
运行结果:
sql
生成的 SQL: SELECT name, salary FROM employees WHERE department='工程'
('李四', 84000)
('赵六', 80000)
三、Schema 提取:怎么告诉模型数据库长啥样?
大模型没见过你的数据库,你得把**表结构(schema)**告诉它,它才能写出正确的 SQL。提取 schema 有两种方式:
方式一:sqlite_master 直接拿建表语句
python
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
schema_str = "\n".join([table[0] for table in tables])
输出:
sql
CREATE TABLE employees(id INTEGER PRIMARY KEY, name TEXT, department TEXT, salary INTEGER)
CREATE TABLE departments(id INTEGER PRIMARY KEY, name TEXT, manager TEXT)
方式二:PRAGMA table_info 自己拼
python
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"
schema_str += "\n".join([f" {col[1]} {col[2]}" for col in schema])
schema_str += "\n);\n\n"
输出:
sql
CREATE TABLE employees (
id INTEGER
name TEXT
department TEXT
salary INTEGER
);
两种方式对比
| 特性 | sqlite_master | PRAGMA table_info |
|---|---|---|
| 代码量 | 2 行 | 5+ 行 |
| 输出完整性 | 完整建表语句(含约束) | 只有列名和类型 |
| 可定制性 | 低,原样输出 | 高,可以自由拼接格式 |
| 适用场景 | 快速原型 | 需要精简 schema 时 |
💡 面试回答: sqlite_master 适合快速拿到完整 schema,PRAGMA 适合需要定制输出格式的场景。实际项目中,如果表结构复杂,还会做进一步精简,只保留模型需要的字段信息。
四、Prompt 模板设计:让模型输出靠谱的 SQL
Prompt 是 Text2SQL 的灵魂。模板设计得好,模型输出的 SQL 就准;设计得烂,模型可能给你输出一堆废话。
核心要素
一个好的 Text2SQL prompt 必须包含:
sql
┌─────────────────────────────────────┐
│ 1. 角色定义(可选但推荐) │
│ "你是一个 SQL 专家" │
│ │
│ 2. 数据库 schema │
│ CREATE TABLE employees (...) │
│ │
│ 3. 输出约束 │
│ "只输出 SQL,不要 Markdown" │
│ │
│ 4. 用户问题 │
│ "工程部门员工的姓名和工资是多少?" │
└─────────────────────────────────────┘
❌/✅ 对比
python
# ❌ 错误:prompt 太模糊,模型可能输出一堆解释
prompt = f"帮我写个 SQL,查:{query}"
# ❌ 错误:没有给 schema,模型不知道表结构
prompt = f"根据以下问题写 SQL:{query}"
# ✅ 正确:有 schema、有输出约束、有问题
prompt = f"""
这是一个数据库的schema:
{schema}
根据这个schema,请输出一个SQL查询来回答以下问题。
只输出SQL查询语句本身,不要使用任何Markdown格式,
不要包含反引号、代码块标记或额外说明。
问题:{query}
"""
进阶技巧
| 技巧 | 说明 | 示例 |
|---|---|---|
| Few-shot 示例 | 给几个问答对,让模型学格式 | "问题:平均工资?→ SQL:SELECT AVG(salary)..." |
| 限定输出格式 | 严格约束输出内容 | "只输出 SQL,不要任何解释" |
| 复杂查询提示 | 提前告知可能用到的语法 | "可能需要用到 JOIN 和 GROUP BY" |
| 错误修正 | 拿到 SQL 后验证,报错再让模型修 | 执行报错 → 把错误信息再喂给模型 |
⚠️ 面试考点: Text2SQL 的核心难点不在代码实现,而在 prompt 工程 和 schema 设计。如何让模型准确理解用户的意图、如何处理多表关联、如何应对模糊查询,这些才是实际项目中需要解决的问题。
五、常见坑 / 易错点
1. 忘记 commit
python
cursor.execute("INSERT INTO employees VALUES (1, '张三', '销售', 50000)")
# ❌ 忘了 commit,数据没写进去
conn.commit() # ✅ 加上这行
2. UNIQUE constraint failed
python
# ❌ 重复插入相同 ID
cursor.executemany("INSERT INTO employees VALUES (?,?,?,?)", sample_data)
# ✅ 方案一:先清空
cursor.execute("DELETE FROM employees")
# ✅ 方案二:跳过重复
cursor.executemany("INSERT OR IGNORE INTO employees VALUES (?,?,?,?)", sample_data)
3. 中文逗号
python
# ❌ 中文逗号,SyntaxError
data = [(1, "张三","销售", 50000)]
# ✅ 英文逗号
data = [(1, "张三", "销售", 50000)]
4. 模型输出带 Markdown
模型有时会输出 sql ... 包裹的代码块,没法直接执行。解决办法:
- prompt 里明确写"不要使用任何 Markdown 格式"
- 或者拿到结果后用正则提取
六、面试高频 N 问
Q1:Text2SQL 的核心流程是什么?
三个步骤:
- 提取 Schema --- 从数据库获取表结构信息
- 构造 Prompt --- 把 Schema + 用户问题组装成 prompt 发给大模型
- 执行 SQL --- 拿到模型生成的 SQL,在数据库上执行并返回结果
Q2:为什么要把 Schema 喂给模型?
大模型的训练数据里没有你的数据库信息,它不知道你有哪些表、哪些字段。Schema 就是告诉模型"数据库长什么样",它是模型写出正确 SQL 的前提。没有 Schema,模型只能瞎猜。
Q3:如何提高 Text2SQL 的准确率?
几个方向:
-
优化 Prompt:加 few-shot 示例、明确输出约束
-
精简 Schema:只给相关表和字段,减少干扰
-
多轮对话:SQL 不对时,把报错信息反馈给模型修正
-
后处理:对生成的 SQL 做语法检查和安全校验
Q4:Text2SQL 有哪些局限性? -
复杂查询(多层嵌套、子查询)准确率下降
-
模糊表述("最近的订单"------"最近"是多久?)需要额外处理
-
安全风险:生成的 SQL 可能包含 DROP、DELETE 等危险操作,需要做权限控制和 SQL 审计
Q5:为什么用 SQLite 而不是 MySQL?
教学和原型阶段用 SQLite 最方便------零配置、Python 内置、一个文件就是数据库。生产环境当然会换成 MySQL/PostgreSQL,但 Text2SQL 的核心逻辑(提取 Schema → 构造 Prompt → 生成 SQL → 执行)是通用的,换数据库只需要改连接方式。
总结
核心概念速查表
| 概念 | 一句话解释 |
|---|---|
| SQLite | 一个文件就是一个数据库,零配置,Python 内置 |
| Schema | 数据库的表结构描述,告诉模型"有哪些表、哪些字段" |
| Text2SQL | 用大模型把自然语言翻译成 SQL 语句 |
| Prompt 模板 | Schema + 输出约束 + 用户问题,三者缺一不可 |
| commit | 把内存中的修改写入磁盘,不 commit 数据就丢了 |
| executemany | 批量插入,比 execute 逐条插更高效 |
核心代码骨架
python
# 1. 提取 Schema
cursor.execute("SELECT sql FROM sqlite_master WHERE type='table'")
schema = "\n".join([t[0] for t in cursor.fetchall()])
# 2. 构造 Prompt
prompt = f"数据库 schema:\n{schema}\n问题:{question}\n只输出 SQL。"
# 3. 调用大模型
response = client.chat.completions.create(model="deepseek-v4-flash", messages=[...])
sql = response.choices[0].message.content
# 4. 执行 SQL
results = cursor.execute(sql).fetchall()
一句话总结
Text2SQL 的本质 = 给模型一份"数据库说明书"(Schema)+ 一个问题 → 模型帮你写出 SQL。
结尾
这篇文章基于我学习 Text2SQL 的完整过程,从 SQLite 基础到完整实现,希望能帮到同样在学大模型应用开发的同学。
完整代码已整理到项目中,有问题欢迎在评论区交流 🔥
如果觉得有帮助,点个赞收藏一下,后续会继续更新大模型应用开发的实战内容~