Text2SQL 实战:自然语言直连数据库,让 AI 自动写 SQL 完成 CRUD 与多表联查

Text2SQL 实战:自然语言直连数据库,让 AI 自动写 SQL 完成 CRUD 与多表联查

不懂 SQL 也能查数据库?"工程部门员工的姓名和工资是多少?"------一句人话,AI 自动生成 SELECT name, salary FROM EMPLOYEES WHERE department = '工程'。这就是 Text2SQL:自然语言 → SQL 的自动翻译。本文从零搭建一个完整的 Text2SQL 项目:SQLite 文件数据库建表 → Schema 工程化提取 → DeepSeek 生成 SQL → CRUD 四种操作实战 → 多表 JOIN + GROUP BY 聚合查询。核心只有一个函数 ask_deepseek,却能覆盖增删改查的全部场景。建议收藏后动手实操。


一、Text2SQL 是什么?

1.1 从"会写代码"到"会说话"

sql 复制代码
Text2SQL = 自然语言 → SQL 语句

  传统方式:
  → 想查数据,必须先会 SQL
  → 想改数据,必须手写 INSERT / UPDATE
  → 数据库操作有门槛

  Text2SQL 方式:
  → 用自然语言描述需求
  → AI 自动生成 SQL
  → 直接执行

  举例:
  ┌──────────────────────────────────────────────────────────┐
│  自然语言                        AI 生成的 SQL            │
│                                                          │
│  "工程部门员工的姓名和工资"    →  SELECT name, salary     │
│                                  FROM employees          │
│                                  WHERE department='工程'  │
│                                                          │
│  "增加一个新员工张三"          →  INSERT INTO employees   │
│                                  (name, department,      │
│                                   salary)                │
│                                  VALUES ('张三',...);    │
│                                                          │
│  "把王二的工资调到 55000"      →  UPDATE employees        │
│                                  SET salary=55000        │
│                                  WHERE name='王二'        │
│                                                          │
│  "删除市场部的王二"            →  DELETE FROM employees   │
│                                  WHERE department='市场'  │
│                                  AND name='王二'          │
└──────────────────────────────────────────────────────────┘

1.2 Text2SQL 的本质

sql 复制代码
Text2SQL 的本质 = 两个技术栈的碰撞

  ① NLP(自然语言处理)
  → 理解用户意图
  → 提取查询条件
  → 解析语义

  ② 数据库技术
  → 理解表结构(Schema)
  → 生成合法 SQL
  → 执行查询

  LLM 时代的 Text2SQL:
  → 不需要复杂的 NLP 模型
  → 不需要规则引擎
  → 一个 LLM + 一段好 prompt 就能搞定

  核心公式:
  Text2SQL = Schema + Prompt + LLM

  ┌──────────────────────────────────────────────────────────┐
│  Text2SQL 的平权意义                                      │
│                                                          │
│  vibe coding 平权了代码开发                              │
│  → 不会编程也能让 AI 写代码                               │
│                                                          │
│  text2sql 平权了数据库操作                                │
│  → 不会 SQL 也能查数据库                                  │
│  → 用自然语言描述需求即可                                 │
│                                                          │
│  这就是"数据库平权"                                       │
│  → 业务人员直接问数据                                     │
│  → 不需要经过开发人员转述                                 │
└──────────────────────────────────────────────────────────┘

二、SQLite:零配置的文件数据库

2.1 为什么选 SQLite?

arduino 复制代码
SQLite 是什么?

  → 系统内置的文件型关系型数据库
  → 一个 .db 文件就是整个数据库
  → 轻量级数据库引擎
  → 无需单独安装
  → 无需配置
  → 无需管理

  ┌──────────────────────────────────────────────────────────┐
│  SQLite vs MySQL 对比                                    │
│                                                          │
│              SQLite                MySQL                  │
│  ─────────────────────────────────────────────            │
│  类型       文件型(.db 文件)     服务器型                 │
│  安装       无需安装               需要安装配置            │
│  连接       本地文件               网络连接(host/port)   │
│  启动       无需启动               需要启动服务            │
│  场景       单机/嵌入式/原型       生产环境/并发/多用户     │
│  并发       弱(写锁)             强(行级锁)            │
│  适合       Python 项目内置         Web 应用               │
│                                                          │
│  Python 内置 sqlite3 模块                                │
│  → 不需要 pip install                                    │
│  → import sqlite3 直接可用                               │
│  → 原型验证 Text2SQL 的最佳选择                          │
└──────────────────────────────────────────────────────────┘

2.2 连接数据库与创建表

python 复制代码
# 文件型的关系型数据库
import sqlite3

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

# 创建一个游标对象
# 通过 光标(cursor) 执行 SQL 操作
cursor = conn.cursor()

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

# 批量插入示例数据
sample_data = [
    (6, '张三', '销售', 5000),
    (7, '李四', '市场', 6000),
    (8, '王五', '研发', 7000),
    (9, '赵六', '工程', 8000),
    (10, '王二', '市场', 9000),
]

cursor.executemany(
    'INSERT INTO employees VALUES (?,?,?,?)',
    sample_data
)
conn.commit()
sql 复制代码
代码解析:

  ① sqlite3.connect('test.db')
  → 连接数据库
  → 文件不存在会自动创建
  → 这就是"文件型数据库"的含义

  ② cursor = conn.cursor()
  → 创建游标对象
  → 游标(cursor)是执行 SQL 的操作手柄
  → 所有 SQL 都通过 cursor 执行

  ③ cursor.execute(sql)
  → 执行单条 SQL
  → CREATE TABLE 建表

  ④ cursor.executemany(sql, data)
  → 批量执行 SQL
  → data 是元组列表
  → 每个元组对应一行数据
  → ? 是占位符(参数化,防注入)

  ⑤ conn.commit()
  → 提交事务
  → 不 commit 数据不会真正写入
  → SQLite 默认手动提交

  INSERT INTO employees VALUES (?,?,?,?)
  → ? 占位符
  → executemany 自动绑定每个元组
  → 防止 SQL 注入

2.3 事务 Transaction

sql 复制代码
readme 中的事务概念:

  insertmany → INSERT INTO XXX VALUES (?, ?, ?, ?)

  事务(Transaction):
  → 一组操作的集合
  → 要么全部成功,要么全部失败
  → 保证数据一致性

  经典案例(下单流程):
  ┌──────────────────────────────────────────────────────────┐
│  下单事务:三个步骤                                       │
│                                                          │
│  ① 订单表插入订单(order)                               │
│  → INSERT INTO orders ...                                │
│                                                          │
│  ② 商品表库存减一(product count-1)                     │
│  → UPDATE products SET count = count - 1                 │
│                                                          │
│  ③ 付款记录(pay)                                       │
│  → INSERT INTO payments ...                              │
│                                                          │
│  事务保证:                                               │
│  → 三步全部成功 → commit 生效                            │
│  → 任一步失败 → rollback 回滚                            │
│  → 不会出现"扣了库存没付款"的情况                        │
└──────────────────────────────────────────────────────────┘

  Python 中的事务:
  conn.commit()   # 全部成功,提交
  conn.rollback() # 失败回滚

  为什么需要事务?
  → 数据库操作可能中途失败
  → 部分成功 = 数据不一致
  → 事务保证"全有或全无"

三、Schema 工程:让 AI 看懂数据库

3.1 为什么需要 Schema?

sql 复制代码
LLM 不知道你的数据库长什么样

  → LLM 是"通用智能"
  → 它不知道你有 employees 表
  → 不知道有 name / department / salary 字段
  → 不知道 salary 是 INTEGER

  要生成正确的 SQL
  → 必须告诉 LLM 表结构
  → 这个"表结构描述"就是 Schema
  → Schema 是 LLM 理解数据库的桥梁

  类比:
  → 让新员工干活,先给 TA 看组织架构图
  → 让 AI 写 SQL,先给它看表结构
  → Schema = 数据库的"组织架构图"

3.2 用 PRAGMA 提取 Schema

python 复制代码
# 获取数据库 Schema
# 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)
sql 复制代码
输出结果:

  PRAGMA table_info(employees) 返回:
  [(0, 'id', 'INTEGER', 0, None, 1),
   (1, 'name', 'TEXT', 0, None, 0),
   (2, 'department', 'TEXT', 0, None, 0),
   (3, 'salary', 'INTEGER', 0, None, 0)]

  每个元组的含义:
  (cid, name, type, notnull, dflt_value, pk)
  → 0: 列序号
  → 'id': 列名
  → 'INTEGER': 数据类型
  → 0: 是否非空
  → None: 默认值
  → 1: 是否主键

  转换后的 Schema 字符串:
  CREATE TABLE EMPLOYEES (
  id INTEGER
  name TEXT
  department TEXT
  salary INTEGER
  )

  关键代码:
  col[1]  → 列名(第 1 个元素)
  col[2]  → 类型(第 2 个元素)
  f'{col[1]} {col[2]}' → "id INTEGER"
  '\n'.join([...]) → 每列一行拼接

3.3 Schema 工程的核心思想

sql 复制代码
Schema 为什么是"工程"?

  ┌──────────────────────────────────────────────────────────┐
│  Schema 的作用:                                         │
│                                                          │
│  ① 表名和列名准确                                        │
│  → AI 知道表叫 employees 而不是 staff                    │
│  → AI 知道列叫 department 而不是 dept                    │
│                                                          │
│  ② 类型信息完整                                          │
│  → salary 是 INTEGER(数字)                             │
│  → name 是 TEXT(字符串)                                │
│  → AI 知道该用什么格式的值                                │
│                                                          │
│  ③ 表间关系可推断                                        │
│  → employees.department = departments.name               │
│  → AI 能自动发现关联字段                                │
│  → 才能生成 JOIN 语句                                    │
│                                                          │
│  Schema 质量决定 SQL 质量                                │
│  → Schema 越详细,SQL 越准确                             │
│  → Schema 缺失,AI 只能瞎猜                              │
│  → 这就是"Schema 工程"的重要性                          │
└──────────────────────────────────────────────────────────┘

四、ask_deepseek:Text2SQL 核心函数

4.1 环境配置

python 复制代码
import os
from openai import OpenAI

# 从环境变量读取(不要把密钥写死在代码里!)
client = OpenAI(
    api_key=os.getenv('OPENAI_API_KEY'),
    base_url='https://api.deepseek.com/v1'
)
ini 复制代码
安全提示:

  ❌ 错误做法(密钥硬编码):
  client = OpenAI(api_key='sk-xxxxx')

  ✅ 正确做法(环境变量):
  client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))

  → 密钥写进代码 = 密钥泄露
  → 一旦提交到 GitHub 就废了
  → 用环境变量 / .env 文件管理
  → 生产环境用密钥管理服务

4.2 核心函数实现

python 复制代码
# text2sql 数据库平权
# vibe coding 平权了代码开发
def ask_deepseek(query, schema):
    # 模板:Schema + 问题 → 生成 SQL
    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
sql 复制代码
Prompt 模板设计解析:

  ┌──────────────────────────────────────────────────────────┐
│  Prompt 四要素                                            │
│                                                          │
│  ① 提供 Schema                                           │
│  "这是一个数据库的Schema:{schema}"                       │
│  → AI 知道表结构                                          │
│  → 这是生成正确 SQL 的前提                               │
│                                                          │
│  ② 明确任务                                               │
│  "请输出一个SQL查询来回答以下问题"                       │
│  → 告诉 AI 要做什么                                       │
│  → 目标明确:生成 SQL                                    │
│                                                          │
│  ③ 输出约束(关键!)                                     │
│  "只输出SQL 查询语句本身"                                │
│  "不要使用任何Markdown格式"                              │
│  "不要包含反引号、代码块标记或额外说明"                  │
│  → 防止 AI 返回 ```sql 代码块                             │
│  → 防止 AI 添加解释文字                                  │
│  → 让结果可以直接执行                                     │
│                                                          │
│  ④ 问题输入                                               │
│  "问题:{query}"                                          │
│  → 用户的具体查询需求                                     │
│                                                          │
│  为什么"只输出 SQL"这么重要?                             │
│  → 如果 AI 返回 ```sql\nSELECT...\n```                  │
│  → cursor.execute 会报错(反引号不是合法 SQL)            │
│  → 必须让输出"纯净"                                     │
│  → 直接可执行                                            │
└──────────────────────────────────────────────────────────┘

4.3 调用模型

ini 复制代码
response 的取值路径:

  response
    └── choices          # 候选结果列表(通常一个)
          └── [0]        # 第一个结果
              └── message
                    └── content    # AI 生成的 SQL 文本

  response.choices[0].message.content
  → 就是生成的 SQL 语句字符串

  model='deepseek-v4-flash'
  → DeepSeek 的快速模型
  → 生成 SQL 这类简单任务,快模型足够
  → 速度快、成本低

  max_tokens=2048
  → 限制生成的最大 token 数
  → SQL 通常很短,2048 足够
  → 防止 AI 无限生成

五、CRUD 全家桶实战

5.1 SELECT:查询

python 复制代码
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)
# ('赵六', 8000)
sql 复制代码
执行流程:

  问题:"工程部门员工的姓名和工资是多少?"
    │
    │  ask_deepseek(question, schema_str)
    │  → LLM 解析意图:
    │    "工程部门" → WHERE department = '工程'
    │    "姓名和工资" → SELECT name, salary
    │
    ▼
  生成的 SQL:
  SELECT name, salary FROM EMPLOYEES WHERE department = '工程';
    │
    │  cursor.execute(sql_query).fetchall()
    │
    ▼
  执行结果:
  ('赵六', 8000)

  AI 的正确理解:
  → "工程部门" → department = '工程'(字符串要加引号)
  → "姓名和工资" → 只查这两个字段(不查 *)
  → 字段顺序:name 在前,salary 在后

5.2 INSERT:插入

python 复制代码
question = '在销售部门增加一个新员工,姓名为张三,工资为45000'
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
# INSERT INTO EMPLOYEES (name, department, salary) VALUES ('张三', '销售', 45000);

cursor.execute(sql_query)
conn.commit()
sql 复制代码
INSERT 生成的 SQL 分析:

  问题:"在销售部门增加一个新员工,姓名为张三,工资为45000"

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

  AI 的正确理解:
  → "增加一个新员工" → INSERT INTO
  → "姓名为张三" → name = '张三'
  → "销售部门" → department = '销售'
  → "工资为45000" → salary = 45000
  → 字段名明确列出(id 自增,不需要指定)
  → 字符串加引号,数字不加

  注意:
  → 生成的 SQL 指定了字段列表 (name, department, salary)
  → 没有指定 id(AUTOINCREMENT 自动生成)
  → 这是"正确"的 INSERT 写法

5.3 UPDATE:更新

python 复制代码
question = '将王二的工资调整为55000'
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
# UPDATE EMPLOYEES SET salary = 55000 WHERE name = '王二';

cursor.execute(sql_query)
conn.commit()
sql 复制代码
UPDATE 生成的 SQL 分析:

  问题:"将王二的工资调整为55000"

  UPDATE EMPLOYEES SET salary = 55000 WHERE name = '王二';

  AI 的正确理解:
  → "将...调整" → UPDATE
  → "工资调整为55000" → SET salary = 55000
  → "王二" → WHERE name = '王二'

  关键点:
  → WHERE 条件必须带上!
  → 否则会更新所有行(灾难)
  → AI 能正确识别更新条件

5.4 DELETE:删除

python 复制代码
question = '删除部门为市场的王二'
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
# DELETE FROM EMPLOYEES WHERE department = '市场' AND name = '王二'

cursor.execute(sql_query)
conn.commit()
sql 复制代码
DELETE 生成的 SQL 分析:

  问题:"删除部门为市场的王二"

  DELETE FROM EMPLOYEES WHERE department = '市场' AND name = '王二'

  AI 的正确理解:
  → "删除" → DELETE FROM
  → "部门为市场的" → department = '市场'
  → "王二" → name = '王二'
  → 两个条件用 AND 连接

  关键点:
  → 条件越精确越安全
  → AI 同时用了两个条件
  → 避免误删同名员工
  → 这是"好的 DELETE"

5.5 验证:查询全部

python 复制代码
question = '查询所有员工的信息'
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
# SELECT * FROM EMPLOYEES;

results = cursor.execute(sql_query).fetchall()
for row in results:
    print(row)
# (6, '张三', '销售', 5000)
# (7, '李四', '市场', 6000)
# (8, '王五', '研发', 7000)
# (9, '赵六', '工程', 8000)
# (11, '张三', '销售', 45000)   ← 新增的张三
sql 复制代码
CRUD 全家桶总结:

  ┌──────────────────────────────────────────────────────────┐
│  CRUD 四种操作                                            │
│                                                          │
│  操作      自然语言          生成的 SQL                   │
│  ─────────────────────────────────────────────────        │
│  Create    "增加新员工张三"  INSERT INTO ...             │
│  Read      "工程部门员工"    SELECT ... WHERE ...        │
│  Update    "工资调为55000"   UPDATE ... SET ...          │
│  Delete    "删除市场王二"    DELETE ... WHERE ...        │
│                                                          │
│  一个 ask_deepseek 函数搞定四种操作                       │
│  → 不需要为每种操作写不同代码                            │
│  → LLM 自动判断是增删改查中的哪一种                       │
│  → 这就是 Text2SQL 的威力                                │
└──────────────────────────────────────────────────────────┘

六、多表联查进阶

6.1 创建第二张表

python 复制代码
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()
bash 复制代码
两张表的关系:

  employees 表                        departments 表
  ┌────┬──────┬──────────┬───────┐   ┌────┬──────┬───────┐
│ id │ name │department│salary │   │ id │ name │manager│
├────┼──────┼──────────┼───────┤   ├────┼──────┼───────┤
│ 6  │ 张三 │ 销售     │  5000 │   │ 1  │ 销售 │ 王经理 │
│ 7  │ 李四 │ 市场     │  6000 │   │ 2  │ 工程 │ 李经理 │
│ 8  │ 王五 │ 研发     │  7000 │   │ 3  │ 市场 │ 张经理 │
│ 9  │ 赵六 │ 工程     │  8000 │   └────┴──────┴───────┘
│ 11 │ 张三 │ 销售     │ 45000 │
└────┴──────┴──────────┴───────┘

  关联字段:
  → employees.department = departments.name
  → "销售" / "工程" / "市场" 是连接点
  → 通过这个关系做 JOIN

6.2 完整 Schema 生成(多表)

python 复制代码
# 获取完整的数据库 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:')
print(schema_str)
sql 复制代码
多表 Schema 生成:

  循环遍历所有表:
  → employees → 生成 CREATE TABLE employees (...)
  → departments → 生成 CREATE TABLE departments (...)

  输出:
  CREATE TABLE employees (
  id INTEGER
  name TEXT
  department TEXT
  salary INTEGER
  );

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

  价值:
  → AI 同时看到两张表的结构
  → 才能推断表间关系
  → 才能生成 JOIN 语句
  → 单表 Schema 只能做单表查询

6.3 多表 JOIN + 聚合查询

python 复制代码
question = '根据两个表之间的关系,列出每个部门的员工人数和平均工资'
sql_query = ask_deepseek(question, schema_str)
print(sql_query)
# SELECT d.name AS department, COUNT(e.id) AS employee_count,
#        AVG(e.salary) AS average_salary
# FROM departments d
# LEFT JOIN employees e ON d.name = e.department
# GROUP BY d.name;

results = cursor.execute(sql_query).fetchall()
for row in results:
    print(row)
# ('工程', 1, 8000.0)
# ('市场', 1, 6000.0)
# ('销售', 2, 25000.0)
vbnet 复制代码
这条 SQL 是全篇最高级的查询:

  SELECT d.name AS department,
         COUNT(e.id) AS employee_count,
         AVG(e.salary) AS average_salary
  FROM departments d
  LEFT JOIN employees e ON d.name = e.department
  GROUP BY d.name;

  逐段解析:

  ① SELECT d.name AS department
  → 查部门名称
  → d 是 departments 的别名
  → AS 给列起别名(显示为 department)

  ② COUNT(e.id) AS employee_count
  → 统计每个部门的员工人数
  → COUNT 聚合函数
  → 统计 e.id 非空的记录数

  ③ AVG(e.salary) AS average_salary
  → 计算平均工资
  → AVG 聚合函数
  → 平均值

  ④ FROM departments d
  → 以 departments 为主表
  → 左连接(LEFT JOIN)
  → 保证所有部门都显示(即使没有员工)

  ⑤ LEFT JOIN employees e ON d.name = e.department
  → 关联条件:部门名相等
  → 员工表匹配到对应部门

  ⑥ GROUP BY d.name
  → 按部门分组
  → COUNT 和 AVG 按组计算

  执行结果:
  ('工程', 1, 8000.0)    → 工程部 1 人,平均 8000
  ('市场', 1, 6000.0)    → 市场部 1 人,平均 6000
  ('销售', 2, 25000.0)   → 销售部 2 人,平均 25000
  → 销售部平均 25000 = (5000 + 45000) / 2
  → 数据完全正确!
sql 复制代码
JOIN 方向的选择:

  需求:"列出每个部门的员工人数和平均工资"
  → 主语是"部门"
  → 所有部门都要显示
  → 即使某部门没有员工也要出现(显示 0)

  用 LEFT JOIN(左连接):
  → departments 在左(主表)
  → employees 在右(从表)
  → 左表所有行都保留
  → 右表没有匹配 → 填 NULL

  ┌──────────────────────────────────────────────────────┐
│  LEFT JOIN:                                          │
│  部门(左)            员工(右)                     │
│  销售 ←──────────────  张三、张三(45000)              │
│  工程 ←──────────────  赵六                            │
│  市场 ←──────────────  李四                            │
│  研发 ←──────────────  (员工表没有研发?)            │
│  → 研发也要显示,员工为 0                              │
│  → 这就是 LEFT JOIN 的意义                            │
│                                                      │
│  RIGHT JOIN:                                         │
│  → 以右表(员工)为主                                 │
│  → 员工表所有行都保留                                 │
│  → 没有部门的员工也会显示                             │
│  → 与需求"以部门为主"相反                             │
└──────────────────────────────────────────────────────┘

七、Text2SQL 的工程化与安全

7.1 风险:AI 生成的 SQL 能直接执行吗?

sql 复制代码
Text2SQL 的最大风险 = 写操作

  AI 生成的 SELECT:
  → 只读操作,风险低
  → 查错数据顶多结果不对

  AI 生成的 INSERT / UPDATE / DELETE:
  → 写操作,风险高!
  → 一条错误的 DELETE 可能删光全表
  → 必须人工审查!

  ┌──────────────────────────────────────────────────────────┐
│  安全策略:                                               │
│                                                          │
│  ① 区分读写                                              │
│  → SELECT 可以自动执行                                  │
│  → INSERT / UPDATE / DELETE 必须人工确认                │
│                                                          │
│  ② 只读权限                                              │
│  → 查询场景用只读数据库账号                              │
│  → 即使 SQL 错了也删不了数据                             │
│                                                          │
│  ③ 事务包裹                                              │
│  → 写操作放事务里                                        │
│  → 执行前备份,出错回滚                                 │
│                                                          │
│  ④ SQL 白名单 / 校验                                     │
│  → 检查是否包含 DROP / TRUNCATE 等危险语句              │
│  → 阻止危险操作                                          │
│                                                          │
│  ⑤ 人机协同                                              │
│  → AI 生成 SQL → 人确认 → 执行                         │
│  → 这就是"人在回路"(Human in the Loop)                 │
└──────────────────────────────────────────────────────────┘

7.2 Prompt 工程要点回顾

sql 复制代码
Text2SQL 的 Prompt 设计要点:

  ① Schema 完整
  → 所有表、所有字段、所有类型
  → 这是生成正确 SQL 的基石

  ② 输出纯净
  → "只输出 SQL 语句本身"
  → "不要 Markdown 格式"
  → "不要反引号"
  → "不要额外说明"
  → 保证结果可直接执行

  ③ 任务明确
  → "请输出一个 SQL 查询来回答以下问题"
  → 不拐弯抹角

  ④ 模型选型
  → 简单查询:flash 类快模型(快、便宜)
  → 复杂查询:更强大的模型(准确率高)

7.3 Text2SQL 的应用场景

sql 复制代码
Text2SQL 的落地场景:

  ① 数据分析平权
  → 业务人员直接问数据
  → "上个月销售额是多少?"
  → 不需要懂 SQL

  ② 智能客服
  → 用户查订单状态
  → 自动转 SQL 查询
  → 返回结果给用户

  ③ 自然语言报表
  → "按部门统计平均工资"
  → 自动生成聚合查询
  → 自动出报表

  ④ Agent + 数据库
  → AI Agent 作为 DBA
  → 自主查询、自主分析
  → 这是 RAG 之外的另一条数据通路

八、总结

8.1 知识体系图

sql 复制代码
Text2SQL 完整实战
│
├── Text2SQL 是什么
│   ├── 自然语言 → SQL 语句
│   ├── 核心公式:Schema + Prompt + LLM
│   └── 平权意义:不会 SQL 也能查数据库
│
├── SQLite 文件数据库
│   ├── 文件型(.db),无需安装配置
│   ├── sqlite3.connect('test.db')
│   ├── cursor 游标执行 SQL
│   ├── executemany 批量插入
│   ├── conn.commit() 提交事务
│   └── 事务:订单/商品/付款 全有或全无
│
├── Schema 工程
│   ├── PRAGMA table_info(表名) 提取结构
│   ├── (cid, name, type, notnull, dflt_value, pk)
│   ├── 列表推导式拼接 schema 字符串
│   ├── 多表循环生成完整 schema
│   └── Schema 是 LLM 理解数据库的桥梁
│
├── ask_deepseek 核心函数
│   ├── OpenAI 客户端 + DeepSeek API
│   ├── Prompt 四要素
│   │   ├── Schema 提供表结构
│   │   ├── 明确生成 SQL 的任务
│   │   ├── 输出约束(只输出 SQL,不要 Markdown)
│   │   └── 用户问题输入
│   └── response.choices[0].message.content
│
├── CRUD 全家桶
│   ├── SELECT:查询(WHERE 条件)
│   ├── INSERT:插入(字段列表 + VALUES)
│   ├── UPDATE:更新(SET + WHERE)
│   ├── DELETE:删除(精确 WHERE 条件)
│   └── 一个函数搞定四种操作
│
├── 多表联查进阶
│   ├── 第二张表 departments
│   ├── 关联字段:employees.department = departments.name
│   ├── LEFT JOIN 主表保留所有行
│   ├── GROUP BY 分组
│   ├── COUNT 计数 + AVG 平均
│   └── AS 别名
│
├── 工程化与安全
│   ├── 密钥用环境变量,不硬编码
│   ├── 写操作必须人工审查
│   ├── 只读账号 / 事务包裹 / SQL 校验
│   └── 人机协同(Human in the Loop)
│
└── 应用场景
    ├── 数据分析平权
    ├── 智能客服
    ├── 自然语言报表
    └── Agent + 数据库(AI 当 DBA)

8.2 一句话总结

Text2SQL 的核心公式是 Schema + Prompt + LLM:先用 PRAGMA table_info 提取表结构拼成 Schema 字符串,再通过精心设计的 Prompt(提供 Schema → 明确任务 → 约束输出格式"只输出 SQL 不要 Markdown")让 LLM 生成纯净可执行的 SQL,最后用 cursor.execute 直接执行。一个 ask_deepseek 函数就能覆盖增删改查全部操作:"工程部门员工的姓名和工资"生成 SELECT,"增加一个新员工"生成 INSERT,"工资调整为 55000"生成 UPDATE,"删除市场部的王二"生成 DELETE;多表场景下 AI 甚至能自动生成 LEFT JOIN + GROUP BY + COUNT/AVG 的聚合查询。SQLite 作为零配置的文件数据库,是验证 Text2SQL 的最佳载体。但要记住:SELECT 可以自动执行,INSERT / UPDATE / DELETE 必须人工审查------AI 生成 SQL 的能力再强,写操作的安全底线不能丢。这就是"数据库平权"的正确打开方式:AI 负责写,人负责审。


如果这篇文章对你有帮助,欢迎点赞收藏

相关推荐
Darling噜啦啦1 小时前
LLM 结构化输出进阶:withStructuredOutput 一行封装 Tool Call,从流式输出到 MySQL 落地
langchain·llm
Flynt18 小时前
"只输出 JSON"根本不够:LLM 结构化输出的坑,我实测了 60 次调用
llm·json
xn713319 小时前
Funes Agent Memory 实测:Codex 长期记忆召回、旧记忆污染与 no-answer 边界
人工智能·llm·ai编程
William一直在路上20 小时前
GPT-6 Astra 研究报告——模型能力、API 演进与 Agent/MCP 生态影响分析
人工智能·gpt·llm·openai·astra
玉宇夕落20 小时前
从InMemory内存记忆到文件持久化,手把手教你管理AI的“记忆”
llm
神秘的猪头20 小时前
新版 LangChain Agent 核心架构:State、Context、ToolRuntime 与 Middleware
langchain·llm·fastapi
武子康21 小时前
Agent 的工具没变,SGLang 缓存为什么没命中?
人工智能·llm·agent
MomentYY21 小时前
大模型 Memory 管理:它凭什么知道你之前说过什么?
llm·agent·ai编程
桃西西呀1 天前
换个会话就失忆?拆开 Agent 记忆的 4 层与 4 个流派,附9个坑的自检清单
人工智能·llm·ai编程