实战|用 DeepSeek + SQLite 从零搭建轻量 Text2SQL 查询助手

一、什么是 Text2SQL

Text2SQL 是典型的大模型落地场景:用自然语言提问,AI 自动生成标准 SQL 语句,直接在数据库中执行并返回结果。业务人员不需要学习 SQL 语法,用大白话就能查数据;开发者也可以用它快速搭建数据查询原型。

本文用最轻量化的方案实现完整 Text2SQL 流程:

  • 数据库:Python 内置 SQLite,零安装、单文件存储
  • 大模型:DeepSeek,兼容 OpenAI 接口,调用简单
  • 全程无重型框架,100 行左右代码跑通全流程

二、前置准备

  1. Python 3.8+ 环境

  2. DeepSeek API Key(官网申请即可)

  3. 安装官方 OpenAI SDK(DeepSeek 接口完全兼容)

    pip install openai

三、分步实现全过程

3.1 初始化 SQLite 数据库

先创建员工表 employees 和部门表 departments,并插入测试数据。

🚩 经典踩坑:主键重复报错 因为 SQLite 数据库文件会持久化保存,重复执行插入代码会导致主键冲突,抛出 UNIQUE constraint failed: employees.id。 解决方案:插入前先清空旧数据,或使用 INSERT OR IGNORE 跳过重复记录。

python 复制代码
import sqlite3

# 连接数据库,文件不存在自动创建
conn = sqlite3.connect("test.db")
cursor = conn.cursor()

# 1. 创建员工表
cursor.execute("""
    CREATE TABLE IF NOT EXISTS employees(
        id INTEGER PRIMARY KEY,
        name TEXT,
        department TEXT,
        salary INTEGER
    )
""")

# 清空旧数据,避免重复执行主键冲突
cursor.execute("DELETE FROM employees")
conn.commit()

# 插入测试数据
employee_data = [
    (6, "黄佳", "销售", 50000),
    (7, "张三", "工程", 75000),
    (8, "黄仁勋", "市场", 90000)
]
cursor.executemany("INSERT INTO employees VALUES (?,?,?,?)", employee_data)

# 2. 创建部门表
cursor.execute("""
    CREATE TABLE IF NOT EXISTS departments(
        id INTEGER PRIMARY KEY,
        name TEXT,
        manager TEXT
    )
""")
cursor.execute("DELETE FROM departments")
conn.commit()

department_data = [
    (1, "销售", "王经理"),
    (2, "工程", "力经理"),
    (3, "市场", "张经理")
]
cursor.executemany("INSERT INTO departments VALUES (?,?,?)", department_data)
conn.commit()
print("数据库初始化完成")

3.2 自动获取数据库 Schema

大模型生成 SQL 的前提是知道表结构 。我们通过 SQLite 内置的 PRAGMA table_info() 自动读取字段信息,拼接成标准建表语句格式,作为 Prompt 上下文传给大模型。

python 复制代码
def get_db_schema(table_list):
    """自动获取指定表的结构,拼接成 Schema 字符串"""
    schema_str = ""
    for table in table_list:
        # 查询表的字段信息:cid, name, type, notnull, dflt_value, pk
        columns = cursor.execute(f"PRAGMA table_info({table})").fetchall()
        # 拼接字段行
        col_lines = [f"{col[1]} {col[2]}" for col in columns]
        # 拼成建表语句格式
        schema_str += f"CREATE TABLE {table} (\n" + ",\n".join(col_lines) + "\n);\n\n"
    return schema_str

# 获取两张表的完整 Schema
schema_str = get_db_schema(["employees", "departments"])
print("完整数据库 Schema:")
print(schema_str)

输出效果:

sql 复制代码
CREATE TABLE employees (
id INTEGER,
name TEXT,
department TEXT,
salary INTEGER
);

CREATE TABLE departments (
id INTEGER,
name TEXT,
manager TEXT
);

3.3 封装 DeepSeek 调用:Prompt 是核心

这一步是 Text2SQL 效果好坏的关键。很多人跑不起来,本质都是 Prompt 没写对。

🚩 高频踩坑点

  1. 字段幻觉:模型编造不存在的字段名 → Schema 必须准确
  2. 值翻译错误 :数据库存中文 "工程",模型生成 'Engineering' → Prompt 强制说明值为中文
  3. 输出带格式:返回带 ```sql 代码块 → 明确要求只输出纯 SQL
  4. 多余解释:模型附带文字说明 → 严格限制输出范围
ini 复制代码
from openai import OpenAI

# 初始化 DeepSeek 客户端
client = OpenAI(
    api_key="你的DeepSeek_API_Key",
    base_url="https://api.deepseek.com/v1"
)

def ask_deepseek(query, schema):
    prompt = f"""
这是数据库的表结构:
{schema}

重要规则:
1. department 字段存储的是中文部门名称,例如:销售、工程、市场,不要翻译成英文
2. 只输出纯 SQL 语句本身,不要任何 Markdown 格式、反引号、代码块标记
3. 不要任何额外解释、说明文字,只返回 SQL

根据上面的表结构,生成 SQL 回答以下问题:
{query}
    """
    response = client.chat.completions.create(
        model="deepseek-chat",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )
    # 去掉首尾空格换行,避免多余字符影响执行
    return response.choices[0].message.content.strip()

3.4 执行 SQL 并输出结果

把模型生成的 SQL 直接交给 SQLite 执行,加上异常捕获,方便定位问题。

python 复制代码
def run_sql(sql):
    try:
        result = cursor.execute(sql).fetchall()
        # 如果是写操作,提交事务
        if sql.strip().upper().startswith(("INSERT", "UPDATE", "DELETE")):
            conn.commit()
        return result
    except Exception as e:
        return f"SQL 执行错误:{e}"

3.5 效果测试

测试 1:查询类问题

scss 复制代码
question = "工程部门员工的姓名和工资是多少?"
sql = ask_deepseek(question, schema_str)
print("生成 SQL:", sql)
print("查询结果:")
for row in run_sql(sql):
    print(row)

预期生成 SQL:

ini 复制代码
SELECT name, salary FROM employees WHERE department = '工程';

输出结果:

arduino 复制代码
('张三', 75000)

测试 2:写入类问题

scss 复制代码
question = "在销售部门增加一个新员工,姓名为李四,工资为45000"
sql = ask_deepseek(question, schema_str)
print("生成 SQL:", sql)
print("执行结果:", run_sql(sql))

测试 3:多表查询

scss 复制代码
question = "查询所有部门的信息"
sql = ask_deepseek(question, schema_str)
print("生成 SQL:", sql)
print("查询结果:")
for row in run_sql(sql):
    print(row)

四、完整可运行代码

python 复制代码
import sqlite3
from openai import OpenAI

# ========== 1. 数据库初始化 ==========
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
    )
""")
cursor.execute("DELETE FROM employees")
employee_data = [
    (6, "黄佳", "销售", 50000),
    (7, "张三", "工程", 75000),
    (8, "黄仁勋", "市场", 90000)
]
cursor.executemany("INSERT INTO employees VALUES (?,?,?,?)", employee_data)

# 部门表
cursor.execute("""
    CREATE TABLE IF NOT EXISTS departments(
        id INTEGER PRIMARY KEY,
        name TEXT,
        manager TEXT
    )
""")
cursor.execute("DELETE FROM departments")
department_data = [
    (1, "销售", "王经理"),
    (2, "工程", "力经理"),
    (3, "市场", "张经理")
]
cursor.executemany("INSERT INTO departments VALUES (?,?,?)", department_data)
conn.commit()

# ========== 2. Schema 获取 ==========
def get_db_schema(table_list):
    schema_str = ""
    for table in table_list:
        columns = cursor.execute(f"PRAGMA table_info({table})").fetchall()
        col_lines = [f"{col[1]} {col[2]}" for col in columns]
        schema_str += f"CREATE TABLE {table} (\n" + ",\n".join(col_lines) + "\n);\n\n"
    return schema_str

schema_str = get_db_schema(["employees", "departments"])

# ========== 3. DeepSeek 封装 ==========
client = OpenAI(
    api_key="你的DeepSeek_API_Key",
    base_url="https://api.deepseek.com/v1"
)

def ask_deepseek(query, schema):
    prompt = f"""
这是数据库的表结构:
{schema}

重要规则:
1. department 字段存储中文部门名称,不要翻译成英文
2. 只输出纯 SQL,不要 Markdown、反引号、解释文字
3. 表名和字段名严格使用上面给出的名称

问题:{query}
    """
    response = client.chat.completions.create(
        model="deepseek-chat",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content.strip()

# ========== 4. SQL 执行 ==========
def run_sql(sql):
    try:
        result = cursor.execute(sql).fetchall()
        if sql.strip().upper().startswith(("INSERT", "UPDATE", "DELETE")):
            conn.commit()
        return result
    except Exception as e:
        return f"执行错误:{e}"

# ========== 5. 测试 ==========
if __name__ == "__main__":
    q1 = "工程部门员工的姓名和工资是多少?"
    sql1 = ask_deepseek(q1, schema_str)
    print("Q1 SQL:", sql1)
    print("Q1 结果:", run_sql(sql1), "\n")

    q2 = "查询所有部门的经理是谁"
    sql2 = ask_deepseek(q2, schema_str)
    print("Q2 SQL:", sql2)
    print("Q2 结果:", run_sql(sql2))

    conn.close()

五、避坑指南

  1. 主键冲突:重复执行插入代码必报错,开发调试时先清空表再插入
  2. 字段名不一致 :表字段是 dept 就不要让模型猜 department,Schema 必须和真实表完全一致
  3. 中文值翻译:数据库存中文就必须在 Prompt 里强调,模型默认会翻译成英文
  4. 输出格式污染:必须明确要求 "只输出纯 SQL",否则带代码块会执行失败
  5. 安全风险 :生产环境必须拦截 DROP、ALTER、DELETE FROM 表 等危险 SQL,不能直接执行用户生成的语句

六、扩展优化方向

  1. 结果自然语言化:把查询结果再传给大模型,转成通顺的中文回答
  2. 少样本提示:在 Prompt 里加入 2-3 组「问题 - SQL」示例,准确率大幅提升
  3. 多表联查:补充表与表之间的关联关系说明,支持 JOIN 查询
  4. 错误自修复:SQL 执行报错后,把错误信息回传给大模型,让它自动修正重写

这个轻量化方案非常适合快速原型验证、内部小工具开发,后续可以基于它扩展出更复杂的数据分析助手。

相关推荐
王中阳Go1 小时前
自己摸了 2 个月零 offer,补底子只用了 3 块:Go 后端转 AI 最难的不是技术
后端·agent·ai编程
高频因子挖掘机1 小时前
第一次补历史行情:按股票拆,还是按日期拆请求?
后端·github·api
小园子的小菜1 小时前
Python协程深度解析:从原理演进到实战避坑
后端·python
高频因子挖掘机1 小时前
300 只股票 × 两年日 K:为什么逐只循环会变成维护灾难?
后端·github·api
苏三说技术2 小时前
Spring AI Alibaba已停更了,Java还有希望吗?
后端
我才是银古2 小时前
单模型执行器 与 OpenCode v2配置迁移 笔记
deepseek·ai平台·opencode
松就是我902983 小时前
如何搭建一个 Agent 系统:方法论、理想形态与一次真实落地
后端
Layer3 小时前
即时通讯系统的关键设计
后端·架构
墨天梦3 小时前
B10_文件SharedPreferences与SQLite
jvm·数据库·sqlite