用 SQLite + 大模型做一个 Text2SQL 小助手:从建表到自然语言查询的完整实战

0 阅读7分钟

前言

你有没有想过这样一个场景:老板突然发消息问“上个月销售部门平均工资是多少”,你不想打开数据库客户端,不想手写 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 工程的关键细节,新手特别容易忽略:

  1. 明确输出格式:一定要强调「只输出 SQL,不要 Markdown」。否则大模型很爱给你包成 sql ...,直接执行会报错。
  2. Schema 放在问题前面:这样模型可以先「读题」再「作答」,生成更稳定。
  3. 越简单的 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

注意这里的三个关键点

  1. 关联字段靠命名推断employees.departmentdepartments.name 名字对上了,模型才能猜出关联关系。这就是为什么「字段命名规范」在 Text2SQL 场景里极其重要。
  2. LEFT JOIN vs INNER JOIN:模型选了 LEFT JOIN,保证即使某部门没有员工也能列出来,这就是「业务语义」比 SQL 语法更难的地方。
  3. 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 做私有化

技术在飞,但方向很清晰:未来的软件,会说话的人就能操作数据

你准备好把这句话变成现实了吗?🚀