大模型实战:手把手教你用 DeepSeek 打造 Text2SQL 数据平权应用

前言

在"Vibe Coding"(氛围编程)时代,代码开发的平权正在发生,而数据平权(Data Democracy) 也紧随其后。Text2SQL 技术让非技术人员只需通过自然语言描述,就能直接与数据库对话并获取结果。

今天,我们将基于 SQLiteDeepSeek API,从零构建一个轻量级的 Text2SQL 系统。本文不仅包含核心代码实现,还会分享在实际开发中遇到的"数据库锁"、"多表 Schema 缺失"以及"事务处理"等实战踩坑经验。


一、 基础环境与数据准备

1.1 为什么选择 SQLite?

正如我们的 readme106.md 笔记中提到的:

作为系统内置的文件型数据库,SQLite 是一个轻量级的数据库,无需安装,无需配置,无需管理。

这让它成为我们验证 Text2SQL 逻辑的最佳沙盒。

1.2 初始化大模型客户端

我们通过 OpenAI SDK 兼容的方式接入 DeepSeek:

python

ini 复制代码
from openai import OpenAI
client = OpenAI(
    api_key="sk-8dbb...", # 你的 DeepSeek API Key
    base_url="https://api.deepseek.com/v1"
)

1.3 创建员工表并插入数据

我们创建一个 employees 表,并尝试批量插入数据。

python

python 复制代码
import sqlite3 
conn = sqlite3.connect("test.db")
# 创建一个游标对象,执行sql操作
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS employees (
    id INTEGER PRIMARY KEY,
    name TEXT,
    department TEXT,
    salary INTEGER
)
""")

sample_data = [
    (6, "胡航", "销售", 50000),
    (7, "牛奶", "工程", 75000),
    (8, "倩倩", "销售", 60000),
    (9, "月月", "工程", 80000),
    (10, "黄仁勋", "市场", 55000),
]
# insertmany 批量插入,提升性能
cursor.executemany(
    "INSERT INTO employees VALUES (?,?,?,?)",
    sample_data
)
conn.commit()

⚠️ 踩坑提示: 在实际运行 Notebook 时,很容易遇到 OperationalError: database is locked。这是因为 SQLite 默认写锁机制较严格,特别是在 Jupyter 反复执行同一个 Cell 或存在未关闭的连接时。建议在重跑前 conn.close(),或者确保每次只运行一次初始化逻辑。


二、 构建 Text2SQL 核心引擎

2.1 提取数据库 Schema

大模型需要知道表结构才能写 SQL。注释中写得非常清楚:Schema 应详细描述每个表的字段和类型,这有助于模型理解表的结构和关联。

python

python 复制代码
# 获取数据库Schema
# SQLite命令,查看employees表的列名、类型等结构信息。
schema = cursor.execute("PRAGMA table_info(employees)").fetchall()
print(schema)

# 列表推导式
# 使用 f-string(格式化字符串字面量),将列名和类型拼接成一个字符串。
schema_str = "CREATE TABLE EMPLOYEES (\n" + "\n".join([f"{col[1]} {col[2]}" for col in schema]) + "\n)"
print("数据库Schema:")
print(schema_str)

2.2 定义 Prompt 与 DeepSeek 调用

python

ini 复制代码
# 销售部门平均工资多少?先分组再算平均
# text2sql 数据平权
# vibe coding 平权了代码开发
def ask_deepseek(query, schema):
    # f 作为模版
    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

三、 CRUD 实战演练

接下来我们看看大模型对于自然语言的 SQL 生成能力。

  • 查询操作
    question = "工程部门员工的姓名和工资是多少?"
    模型输出:SELECT name, salary FROM EMPLOYEES WHERE department = '工程';
  • 插入操作
    question = "在销售部门增加一个新员工,姓名为张三,工资为45000"
    模型输出:INSERT INTO EMPLOYEES (name, department, salary) VALUES ('张三', '销售', 45000);
  • 更新操作
    question = "将倩倩的工资调整为55000"
    模型输出:UPDATE EMPLOYEES SET salary = 55000 WHERE name = '倩倩';
  • ❌ 删除操作的"幻觉"
    question = "删除市场部门的黄仁勋"
    模型却输出了:DELETE FROM EMPLOYEES WHERE id = 12 AND name = '张三';
    分析:大模型在这里产生了严重的幻觉,因为 张三 是上一个上下文插入的,模型将其与 黄仁勋 的删除混淆,并错误地估计了 id=12。这也告诉我们,生产环境中必须对 Text2SQL 生成的增删改语句进行严格校验或引入 Human-in-the-loop。

四、 进阶:多表查询与事务管理

结合 readme106.md 中的设计,我们要进一步引入 departments 表(包含 id, name, manager)。

4.1 唯一性约束踩坑

在尝试插入部门数据时:

python

python 复制代码
cursor.execute("""
CREATE TABLE IF NOT EXISTS departments(
    id INTEGER PRIMARY KEY,
    name TEXT,
    manager TEXT
)
""")
sample_departments = [
    (1, "销售", "王经理"),
    (2, "工程", "李经理"),
    (3, "市场", "张经理")
]
# 执行 executemany 时可能会遇到 IntegrityError

如果多次运行,会遇到 IntegrityError: UNIQUE constraint failed: departments.id。这是因为 CREATE TABLE IF NOT EXISTS 不会清空表,主键冲突会导致插入失败。建议使用 INSERT OR IGNORE 或事先 DROP TABLE

4.2 多表查询的 Schema 盲区

当我们提出需求:question = "根据两个表之间的关系,列出每个部门的员工人数和平均工资"

模型给出:

sql

vbnet 复制代码
SELECT department, COUNT(*) AS employee_count, AVG(salary) AS average_salary FROM EMPLOYEES GROUP BY department;

🔍 发现问题了吗?

生成的 SQL 只用了 EMPLOYEES 表,根本没有连表(JOIN)departments。这是因为我们在构建 schema_str 时,只提取了 employees 表的 Schema ,模型根本不知道 departments 表的存在!这提醒我们,在做多表 Text2SQL 时,提供全量 Schema 至关重要。

4.3 引入事务(Transaction)

针对 readme106.md 中提到的业务场景:

  • 订单 order
  • 商品 product count-1
  • 付款 pay

在 Text2SQL 落地到实际业务时,我们必须引入事务(Transaction) 。单纯让 LLM 生成一句 SQL 是不够的,因为像"生成订单"、"扣减库存"、"付款"往往需要多条 SQL 保持原子性。一旦其中某一步骤失败,必须整体回滚(Rollback),确保数据一致性。

python

php 复制代码
try:
    # 开启事务
    cursor.execute("BEGIN TRANSACTION")
    # 大模型生成的三条相关SQL
    # cursor.execute(sql_order)
    # cursor.execute(sql_product)
    # cursor.execute(sql_pay)
    conn.commit()
except Exception as e:
    conn.rollback()

五、 总结与展望

通过这个实战项目,我们验证了 Text2SQL 数据平权 的可行性。大模型在处理标准 CRUD 甚至部分聚合查询(如 GROUP BY)时已经游刃有余。

但在走向生产的过程中,我们依然需要警惕:

  1. Schema 注入不全:多表查询时模型容易盲目自信,提供完整详尽的 Schema(包括外键关系)是成功的关键。
  2. 大模型的幻觉:如删除操作的乌龙,对于变更类 SQL 必须谨慎。
  3. 事务保障:复杂业务链路不能依赖单条 SQL,必须在应用层利用事务兜底。
相关推荐
全栈弄潮儿1 小时前
让 AI 先写方案,再写代码
aigc·openai·ai编程
plainGeekDev1 小时前
Guides vs Sensors:Harness 的双核控制框架
aigc·ai编程·claude
Behaviour2 小时前
豆包手机助手消费者版发布:首款量产 AI 智能体手机 9 月 16 日开售
人工智能·语言模型·aigc·ai编程
郑州光合科技余经理12 小时前
国际版外卖系统:税率字段怎么和订单主流程解耦
android·java·开发语言·前端·后端·php·ai编程
小虎AI生活14 小时前
微信 AI 社交灰度测试:企业 AI 落地真正的门槛在知识库治理
ai编程
蚕豆糯米饭14 小时前
Github Copilot 研发效能提升实战指南
ai·github·copilot·ai编程
Ray Wang15 小时前
Agent学习
ai编程
程序员老刘17 小时前
Android Studio Quail 4发布,看日志我以为谷歌放弃Flutter了
flutter·android studio·ai编程
VIP_CQCRE17 小时前
在 Visual Studio 里接入 Ace Data Cloud:用 OpenAI 兼容接口提升 AI 编程效率
openai·api·ai编程·visual studio·ace data cloud