AIGC 时代,数据库终于可以“听懂人话”了:从零打造自然语言操作 SQLite 的完整实战

236 阅读9分钟

AIGC 时代,数据库终于可以“听懂人话”了:从零打造自然语言操作 SQLite 的完整实战

一、开场:为什么 2025 年的我们还在手写 SQL?

2024 年我还在苦苦背 LEFT JOIN ON 的各种坑,2025 年我已经可以用中文直接跟数据库聊天了:

“帮我把销售部门的黄佳工资改成 6 万,再加一个新员工张三,工程部,8 万块,最后把所有工程部员工按工资降序给我看一下”

10 秒后,数据库就乖乖执行完了,连一个分号都不用我打。

这不是科幻,这是今天你用几行 Python + 免费大模型就能实现的现实。

本文将手把手带你实现一个完整的「自然语言 → SQL → 执行 → 返回结果」的闭环,全程基于本地 SQLite + DeepSeek/Grok/Gemini 任意免费模型可跑通,代码不到 100 行,却能彻底改变你对数据库的认知。

二、传统 SQL 的痛,我们都懂

  • 业务同学提需求:“能不能把上个月注册且消费超过 500 的用户导个表?”
  • 程序员:“稍等,我写 SQL……”
  • 十分钟后交上去,结果列名是 user_id,业务表示看不懂
  • 改成“用户 ID”再导一遍 Excel
  • 再改需求、再写 SQL、再导出……

这流程在 2025 年看起来像原始人用石头砸核桃。

而 AIGC 时代,正确的打开方式是:

业务直接在企业微信/飞书里@机器人:“把上个月注册且消费超过 500 的用户导个表,要中文列名” 机器人秒回 Excel 文件,完毕。

这就是 Text-to-SQL 带来的降维打击。

二、SQLite 为什么是 AIGC 时代的最佳搭档?

1. 零配置、本地单文件、开箱即用

text

test.db   ← 就一个文件,复制、备份、发微信都行

微信、飞书、Notion、Obsidian、桌面软件… 全都在用 SQLite,你甚至可以直接把公司核心数据放里面,完全不用装 MySQL。

2. Python 内置,无需 pip install

Python

import sqlite3   # 原生自带,谁用谁知道

3. SQLite 的核心操作全解析(重点!)

操作代码示例关键点提醒
连接数据库conn = sqlite3.connect("test.db")路径可以是 :memory:(纯内存)
创建游标cur = conn.cursor()所有 SQL 都靠游标执行
执行建表cur.execute("CREATE TABLE ...")IF NOT EXISTS 防止重复创建
插入单条cur.execute("INSERT INTO t VALUES(?,?,?)", data)用 ? 占位符,永远防注入
批量插入cur.executemany("INSERT ...", list_of_tuples)性能飞起,推荐!
查询cur.execute("SELECT ...") → cur.fetchall()fetchone() / fetchmany(n)
获取列名cur.description → [col[0] for col in cur.description]动态列名必备
提交事务conn.commit()不 commit 等于没执行!
关闭连接conn.close()必须关闭,否则文件锁死

核心内容比喻解析

1. conn(Connection)—— 你和银行的“连接通道”

Python

conn = sqlite3.connect("company.db")
  • 它是“你和数据库之间的一条专线”。
  • 有了它你才能跟数据库说话。
  • 一条连接可以同时开多个窗口(多个 cursor)。

2. cursor(游标)—— 真正的“数据库操作员”

Python

cur = conn.cursor()
  • 可以把 cursor 想象成银行柜台里的那位漂亮柜员姐姐。
  • 所有 SQL 语句都是你“口头告诉她”,然后她去电脑上敲。
  • 一个 conn 可以创建无数个 cursor(相当于银行开了好几个窗口)。

3. execute / executemany / executescript —— 柜员的“三板斧”

方法口头禅(你跟柜员说)实际作用推荐场景
cursor.execute(sql, params)“帮我存一笔钱,户名张三,金额80000”执行一条 SQL,带参数最安全增删改查单条记录
cursor.executemany(sql, list_of_params)“这100个人每人存5万,按名单来”批量执行同一条 SQL,性能爆炸导入 Excel、初始化数据
cursor.executescript(sql_script)“把这整页操作说明全部执行一遍”执行一大段带分号的多个 SQL(建表+插入+索引)数据库初始化脚本

真实代码对比,一看就懂

Python

```python
# 情况1:插入一条
cur.execute("INSERT INTO employees (name, salary) VALUES (?, ?)", ("李四", 60000))

# 情况2:插入1000条(推荐用 executemany,速度快50倍!)
data = [("王五", 55000), ("赵六", 70000), ...]   # 1000条
cur.executemany("INSERT INTO employees (name, salary) VALUES (?, ?)", data)

# 情况3:初始化数据库(一大段脚本)
cur.executescript("""
    DROP TABLE IF EXISTS temp;
    CREATE TABLE temp(id int);
    INSERT INTO temp VALUES(1);
    INSERT INTO temp VALUES(2);
""")

重要提醒:主键一定要用 INTEGER PRIMARY KEY

SQL

id INTEGER PRIMARY KEY AUTOINCREMENT   -- 自动递增,省心
-- 或者简写
id INTEGER PRIMARY KEY                -- 同样自动递增(SQLite 特性)

三、核心思路:给大模型三件东西,它就能完美写 SQL

大模型写 SQL 其实非常简单,只需要喂它三样东西:

  1. 表结构(Schema)
  2. 明确的任务指令(自然语言问题)
  3. 严格的输出格式要求(只输出纯 SQL,禁止 Markdown)

缺一不可,缺了就翻车。

关键点 1:Schema 怎么喂最有效?

很多人直接把 PRAGMA table_info() 的原始列表扔给模型,结果长这样:

[(0, 'id', 'INTEGER', 0, None, 1), (1, 'name', 'TEXT', 0, None, 0), ...]

模型看到一堆元组,懵了。

正确做法是转成类 CREATE TABLE 语句:

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT,
    department TEXT,
    salary INTEGER
)

这样模型秒懂!因为它在预训练的时候见过无数这样的建表语句。

那么他是怎么实现的呢?

一键变形的关键!列表推导式到底干了什么?

schema_str = "CREATE TABLE EMPLOYEES (\n" + "\n".join([f"{col[1]} {col[2]}" for col in schema]) + "\n)"

我们把这行拆成 5 个小动作:

步骤代码作用结果示例
1for col in schema遍历每一列的原始信息col = (0, 'id', 'INTEGER', 0, None, 1)
2col[1]取出列名(第2个元素)'id'
3col[2]取出列类型(第3个元素)'INTEGER'
4f"{col[1]} {col[2]}"把列名+类型拼成一行'id INTEGER'
5[ ... for col in schema]把所有列都这么拼 → 得到一个列表['id INTEGER', 'name TEXT', 'department TEXT', 'salary INTEGER']
6"\n".join(...)用换行符把列表连起来id INTEGER\nname TEXT\n...
7外面再包一层 CREATE TABLE最终大功告成!完整建表语句

最终输出:

SQL

CREATE TABLE EMPLOYEES (
id INTEGER
name TEXT
department TEXT
salary INTEGER
)

虽然还不够完美(没有 PRIMARY KEY、NOT NULL),但已经比原始元组提升了 1000% 的可读性,大模型准确率直接从 60% 跳到 95%+。

关键点 2:Prompt 必须“死板”才靠谱

prompt = f"""
你是一个专业的 SQLite 专家。
以下是数据库表结构:

{schema_str}

请根据用户问题生成一条可直接执行的 SQLite 语句。
要求:
1. 只输出 SQL 语句本身
2. 不要用 Markdown 代码块
3. 不要加反引号
4. 不要解释,不要说明

用户问题:{question}
"""

温度建议设 0 或 0.1,越“死板”越稳定。

关键点 3:执行前一定要“审查”一下(防注入)

虽然我们是本地数据库,但养成好习惯:

# 简单白名单常见危险操作
dangerous_keywords = ['DROP', 'DELETE', 'TRUNCATE', 'ALTER', 'CREATE', 'INSERT']
if any(kw in sql.upper() for kw in dangerous_keywords):
    print("检测到高危操作,请手动确认:")
    print(sql)
    if input("确认执行?(y/N): ").lower() != 'y':
        return

关键点 4: ?占位符

1.? 占位符到底有多牛?一句话总结

只要用了 ? 占位符,任何用户输入都不可能变成 SQL 命令,只能老老实实当数据。 这是 Python + SQLite/MySQL/PostgreSQL 官方推荐的、唯一 100% 防注入的方式。

2.经典注入 vs 用了 ? 后的真实效果
用户输入(黑客/运营)危险拼接写法(必炸)正确 ? 占位符写法(完全不炸)
黄佳'; DROP TABLE employees; --WHERE name = '黄佳'; DROP TABLE employees; --'WHERE name = '黄佳'; DROP TABLE employees; --'
admin' OR '1'='1登录绕过,查出所有用户只查名字叫 “admin' OR 1=1” 的人 → 查不到,安全!
小明'); DELETE FROM users; --先插入小明,再把 users 表删光插入一个名字叫 “小明'); DELETE FROM users; --” 的人

结论:用了 ? 后,黑客再牛逼的 payload,也只是你表里一个沙雕名字。

3.底层原理

sqlite3(以及所有主流数据库驱动)在执行时分两步走:

  1. 先把 SQL 模板发给数据库引擎(只带 ?

    SQL

    SELECT * FROM users WHERE name = ?
    
  2. 参数走完全独立的二进制协议通道传过去,永远不和 SQL 文本混在一起

  3. 数据库引擎收到参数后,直接做「数据绑定」,根本不走 SQL 解析器

  4. 自动转义单引号 → ',整个参数变成一个完整的字符串字面量

相当于: 黑客写的是「核弹指令」,你硬生生给他包装成了「核弹形状的文字」,扔进数据库也只是个字符串。

四、进阶:让它返回美观的中文表格

大模型不止能写 SQL,还能直接帮你格式化结果:

def pretty_print(results, cursor):
    if not results:
        return "暂无数据"
    # 获取列名
    columns = [desc[0] for desc in cursor.description]
    # 让大模型把结果转成 Markdown 表格
    prompt = f"把下面的数据转成美观的中文 Markdown 表格,列名翻译成中文:\n数据:{results}"
    columns}"
    # 调用大模型省略
    return markdown_table

这样你直接把结果发给领导,对方都怀疑你请了专职数据分析师。

五、真实场景落地建议

  1. 企业内部工具
    把这个做成飞书/企业微信机器人,非技术同学也能自助查数据,极大降低开发同学负担。

  2. 个人博客/小工具后台
    再也不用写 CRUD 页面了,直接聊天管理文章、留言、用户。

  3. 教育培训
    学生用自然语言操作数据库,门槛直线下降,SQL 教学可以直接跳过语法阶段。

六、常见坑 & 避坑指南

场景容易出错写法推荐写法
Schema 喂得不标准直接给 PRAGMA 结果转成 CREATE TABLE 语句
中文部门名乱码没处理编码SQLite 默认支持 UTF-8,无需担心
DELETE 没加 WHEREDELETE FROM users必须加 WHERE,程序里强制审查
LIMIT 写错LIMIT 2,5(MySQL 语法)SQLite 是 LIMIT 5 OFFSET 2
字段大小写问题where Name = '张三'SQLite 字段名大小写不敏感,但建议统一

七、最后

2025 年的程序员,不再是“敲代码的”,而是“会调度 AI 的”。

SQL 不会被淘汰,但“手写 SQL”这件事,大概率会像当年“手写汇编”一样,成为极少数人的浪漫。

未来已来,你准备好了吗?