只会 'SELECT * FROM'  的我,用大模型搞了个“SQL 翻译器”

61 阅读6分钟

最近很想玩一玩大模型 + 数据库这套东西,于是干脆写了个简单的代码:

我只说人话,让大模型替我写 SQL,我只负责把 SQL 扔进数据库执行,拿结果。

结果发现,这东西不仅好玩,而且对学习 SQL、理解大模型在业务里的用法,特别有帮助。下面就用尽量“学习者视角”,把这个小玩具拆开讲一遍。

1. 这个小项目到底想解决什么问题?

目标就一句话:

不懂 SQL 的人,也能查库、看数据。

用起来的体验大概是这样:

  • 我输入一句话:
    “工程部门员工的姓名和工资是多少?”

  • 程序内部悄悄干三件事:

    1. 看看数据库长啥样(表结构)
    2. 把“表结构 + 我的问题”丢给大模型,让它生成 SQL
    3. 把这个 SQL 扔回数据库执行,拿到查询结果

整个过程中,我不需要手写 SQL,只需要会说人话

对我这种“还在学习阶段的人”来说,这个过程本身就已经是一个非常好的练习:
我一边看 AI 生成的 SQL,一边对照自己的理解,反而倒过来在学 SQL 的写法。

2. 先来一张最简单的员工表,方便练手

为了让事情简单,我只建了一张员工表,字段也非常直白:

  • id:员工编号
  • name:姓名
  • department:部门
  • salary:工资

用任意一个轻量数据库都行,比如 SQLite。插一点样例数据:工程、销售、市场都有几个人,工资随便写几个不同的数,方便做各种查询、插入、删除的练习。

有了这么一张“小表”,就够我们玩一整套「自然语言 → SQL → 执行 → 结果」的流程了。

3. 先让模型“认识世界”:生成表结构描述(Schema)

大模型虽然“会写 SQL”,但它完全不知道你这张表里具体有哪些字段
所以第一步,一定要把表结构喂给它。

在程序里,我大致做了这么一件事(用伪代码表示):

schema_rows = cursor.execute("PRAGMA table_info(employees)").fetchall()
# schema_rows 里是每一列的名字和类型

schema_str = (
    "CREATE TABLE EMPLOYEES (\n" +
    "\n".join([f"{col_name} {col_type}" for (col_name, col_type) in ...]) +
    "\n)"
)

最终得到类似这样的字符串:

image.png

这段字符串,就是我们之后给大模型的“世界说明书”。
没有它,大模型就只能猜字段名,极容易胡说八道。

学习点:

  • 做 Text-to-SQL 一定记得:表结构(schema)必须显式提供给模型
  • Schema 可以用 SQL 描述形式,模型理解起来更自然。

4. 核心函数:把“人话 + 表结构”交给大模型,换回 SQL

接下来是整个小项目最关键的一步:
写一个函数,给它一个问题 + 表结构,它返回一条 SQL 语句

伪代码大概长这样:

def ask_llm(query, schema):
    prompt = f"""
    这是一个数据库的 Schema:
    {schema}
    根据这个 Schema, 请输出一个 SQL 查询来回答以下问题。
    只输出 SQL 查询语句本身,不要使用任何 Markdown 格式,
    不要包含反引号、代码块标记或额外说明。
    问题:{query}
    """
    response = client.chat.completions.create(
        model="某个聊天模型",
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": prompt
        }],
        temperature=0,  # 稳定输出
    )
    return response.choices[0].message.content.strip()

这里有几个对我这个“学习者”来说特别有价值的点:

  • 把 Schema 写在 prompt 里:模型才能基于真实字段生成 SQL
  • 严格限制输出格式:只许返回 SQL,本地代码更好解析,避免混进解释文字
  • temperature=0:为了让模型尽量“少发挥”,输出更稳定,方便对比和调试

5. 走一遍完整流程:从提问到结果

假设我现在的问题是:

“工程部门员工的姓名和工资是多少?”

逻辑步骤就是:

# 已经有 schema_str 了(前面生成好的那段 CREATE TABLE ...)

question = "工程部门员工的姓名和工资是多少"

# 1. 让模型写 SQL
sql_query = ask_llm(question, schema_str)
print(sql_query)
# 模型可能会返回:
# SELECT name, salary FROM EMPLOYEES WHERE department = '工程';

# 2. 执行 SQL
results = cursor.execute(sql_query).fetchall()
print(results)
# [('宁宁', 75000), ('悦悦', 80000), ('王zc', 10000)]

对我这种正在学习的人来说,最爽的点是

  • 我本来只会模模糊糊说一句:“查工程部门工资啊”
  • 模型帮我写出了完整的 SQL
  • 我再回头去对着 SQL 反推:
    “哦,原来条件是这样写的,字段名这样用,字符串还要加引号”

等于我每问一次问题,就白看一个“标准 SQL 示例”。

6. 不止能查,还能“加人、删人、查全部”

只要 prompt 设计得好,这个思路不仅能查,还能做增删操作。比如:

插入一个新员工

“在销售部门增加一个新员工,姓名为张三,工资为45000”

模型可能给你:

INSERT INTO EMPLOYEES (name, department, salary) VALUES ('张三', '销售', 45000);

你执行一下,再查一下表数据,就能看到“张三”被加进去了。

删除某个员工

“删除市场部门的黄仁勋”

模型可能输出:

DELETE FROM EMPLOYEES WHERE department = '市场' AND name = '黄仁勋';

查询所有人

“查询所有员工的信息”

输出可能是:

SELECT * FROM EMPLOYEES;

对于正在学习的人来说,每一个这样的例子,都是一个非常自然的SQL 练习题 + 参考答案

7. 玩着玩着,会开始关心“工程化的问题”

一开始我只是抱着“玩玩”的心态做了这个小实验,但做着做着,就会自然而然想到一些更“工程向”的问题,比如:

  • 安全性

    • 能不能对生成的 SQL 做检查,禁止 DROP TABLE 这种危险操作?
    • 读写要不要分开?只给“查询接口”给非技术同学用?
  • 可控性

    • 是不是可以只允许 SELECT,写操作必须人工确认?
    • 对生成的 SQL 加上 LIMIT,防止一次性查太多数据?
  • 效率

    • Schema 要不要缓存?
    • 相似的问题要不要加缓存,避免重复请求模型?

这些问题对我来说,本身就是进一步学习“如何在真实项目里用大模型”的好素材。
哪怕现在做不完、做不精,至少面试或者写学习笔记的时候,能说出自己的思路。

8. 对一个“学习者”来说,这个小项目的意义

站在一个还在学习阶段的视角,这个小实验对我最大的帮助有三点:

  • 帮我“反向学 SQL”
    自然语言提问 → 看 AI 写出的 SQL → 对照理解语法和写法。
  • 真实感受“AI First”的开发方式
    不再是写一个“聊天机器人”停在那,而是让模型真正在一个小流程里发挥作用。
  • 多了一个好聊又不太卷的小项目
    不需要大型架构、复杂前后端,只用一个轻量数据库 + 一个大模型 API,就能搭出完整闭环。

如果你也和我一样,还在学习阶段,对 SQL 和大模型都刚刚入门,这样一个小项目非常适合用来练手:

  • 代码量不大
  • 成就感很强
  • 又刚好踩在“未来会越来越常见”的一个方向上

学的时候不一定要一开始就追求“多高级、多通用”,
先让自己真的把这条链路跑通一遍,就已经是很扎实的一步了。