一、为什么要做"自然语言转 SQL"?
在数据分析、低代码平台、BI 工具等场景中,非技术人员常因不会写 SQL 而无法自助查询数据。而借助 LLM(如 DeepSeek、GPT),我们可以:
- 降低使用门槛:用户只需用自然语言提问(如"开发部员工工资多少?")
- 提升开发效率:自动生成准确 SQL,减少人工编写与调试成本
- 快速验证想法:原型阶段无需构建复杂 UI,一句话即可查数据
✅ 本文以 SQLite + DeepSeek API 为例,展示一个最小可行方案(MVP)。
二、技术栈与准备
| 组件 | 说明 |
|---|---|
| 数据库 | SQLite(Python 内置,无需安装服务) |
| LLM 服务 | DeepSeek(兼容 OpenAI API,国内可访问) |
| 编程语言 | Python 3.7+ |
| 依赖库 | sqlite3(内置)、openai(用于调用 DeepSeek) |
安装依赖:
bash
编辑
pip install openai
三、完整实现步骤
步骤 1:创建 SQLite 表并插入示例数据
python
python
编辑
import sqlite3
# 连接数据库(若不存在则自动创建)
conn = sqlite3.connect("text5.db")
cursor = conn.cursor()
# 创建 employees 表
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY, -- 注意:原文 PRINARY 是拼写错误,应为 PRIMARY
name TEXT,
department TEXT,
salary INTEGER
)
""")
# 插入测试数据
sample_data = [
(6, "陈老板", "开发部", 100000),
(7, "张三", "销售部", 20000),
(8, "李四", "开发部", 50000),
(9, "王五", "销售部", 22000),
]
cursor.executemany('INSERT INTO employees VALUES (?, ?, ?, ?)', sample_data)
conn.commit()
⚠️ 注意 :原文中
PRINARY KEY是拼写错误,正确应为PRIMARY KEY,否则会创建成普通列!
步骤 2:提取表结构(Schema)
为了让 LLM 理解表结构,需提供清晰的 Schema 描述:
python
python
编辑
# 获取表字段信息
schema = cursor.execute("PRAGMA table_info(employees)").fetchall()
# 构造 CREATE TABLE 风格的字符串
schema_str = "CREATE TABLE EMPLOYEES (\n" + "\n".join([f" {col[1]} {col[2]}" for col in schema]) + "\n)"
print("数据库 Schema:")
print(schema_str)
输出示例:
sql
sql
编辑
CREATE TABLE EMPLOYEES (
id INTEGER
name TEXT
department TEXT
salary INTEGER
)
💡 虽然缺少约束(如 PRIMARY KEY),但对简单查询已足够。更严谨的做法可解析 DDL 或补充注释。
步骤 3:调用 LLM 生成 SQL
使用 DeepSeek 的 Reasoner 模型(擅长推理任务):
ini
python
编辑
from openai import OpenAI
client = OpenAI(
api_key='sk-xxxx', # 替换为你的 DeepSeek API Key
base_url='https://api.deepseek.com/v1'
)
def ask_deepseek(query: str, schema: str) -> str:
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.content.strip()
# 测试提问
question = "开发部部门员工的姓名和工资是多少?"
sql = ask_deepseek(question, schema_str)
print("生成的 SQL 查询:")
print(sql)
预期输出:
sql
sql
编辑
SELECT name, salary FROM employees WHERE department = '开发部';
步骤 4:执行 SQL 并返回结果(可选)
scss
python
编辑
result = cursor.execute(sql).fetchall()
print("查询结果:", result)
# 输出:[('陈老板', 100000), ('李四', 50000)]
四、关键技巧与注意事项
1. Prompt 设计要点
- 明确指令:"只输出 SQL,不要解释"
- 提供精确 Schema:字段名、类型必须一致
- 示例引导(Few-shot) :复杂场景可加 1~2 个例子提升准确率
2. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 生成无效 SQL | Schema 不清晰或字段名不匹配 | 使用 PRAGMA table_info 精确获取字段 |
| 返回多余文本 | LLM 未严格遵循指令 | 在 Prompt 中强调"仅输出 SQL" |
| 表名大小写错误 | SQLite 默认小写 | Schema 中统一用小写表名 |
| 拼写错误(如 PRINARY) | 手动建表失误 | 建表时仔细检查语法 |
3. 安全警告 ⚠️
- 切勿直接执行 LLM 生成的 SQL!
应进行白名单校验 (如只允许 SELECT)、参数化查询,防止注入攻击。 - 生产环境建议增加 SQL 解析器(如
sqlglot)做语法校验。
五、方案对比:不同实现方式优劣
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| LLM 直接生成 SQL | 快速、灵活、支持自然语言 | 可能出错、需校验 | 原型验证、内部工具 |
| 模板匹配 + 规则引擎 | 稳定、可控 | 扩展性差、需维护规则 | 固定查询模式 |
| 专用 NL2SQL 模型(如 SQLCoder) | 准确率高 | 需部署模型、资源消耗大 | 企业级产品 |
✅ 对于个人项目或 MVP,LLM + Prompt 工程 是性价比最高的选择。
六、总结要点
- ✅ SQLite 轻量易用,适合本地数据存储与测试
- ✅ 通过
PRAGMA table_info可程序化获取表结构 - ✅ LLM(如 DeepSeek)能有效将自然语言转为 SQL
- ✅ Prompt 要清晰、约束要明确
- ❌ 永远不要信任 LLM 输出,必须校验 SQL 安全性
七、拓展思考
- 如何支持多表 JOIN 查询?
→ 在 Schema 中提供多个表的 DDL,并在问题中明确关联字段。 - 能否缓存常见问题的 SQL?
→ 可建立"问题- SQL"映射缓存,提升响应速度与稳定性。 - 如何评估生成 SQL 的准确率?
→ 构建测试集,用执行结果 vs 人工标注结果做比对。 - 前端集成?
→ 用 Flask/FastAPI 封装为 REST API,前端输入问题 → 返回表格数据。