面试被问到 Text2SQL,我用 DeepSeek 自己实现了一个

面试被问到 Text2SQL,我用 DeepSeek 自己实现了一个

面试官问我:"如果让你用 AI 实现自然语言查数据库,你会怎么做?" 我当时答得不太好,回来研究了一下,发现核心原理并不复杂。记录一下。

背景

前段时间面一家后端岗,面试官问了我一个开放题:"现在 AI 这么火,如果让你做一个自然语言查数据库的功能,你怎么设计?"

我支支吾吾说了一些,面试官追问:"那 Schema 怎么传给大模型?提示词怎么设计?多表 JOIN 怎么处理?"

答得不好,挂了。

回来之后我决定把这个东西搞明白。花了一天时间,用 DeepSeek API + SQLite + Python 实现了一个 Text2SQL 的小 demo。过程中把 SQL 的 JOIN 语法也重新过了一遍,顺便整理成这篇文章。


一、SQL JOIN 复习:面试高频考点

面试问 SQL,JOIN 是必考的。我之前一直搞混 INNER JOIN 和 LEFT JOIN 的区别,这次算是彻底弄清楚了。

两张测试表

bash 复制代码
employees(员工表):
| id | name  | department | salary |
|----|-------|------------|--------|
| 1  | 张三  | 销售       | 50000  |
| 2  | 李四  | 工程       | 75000  |
| 3  | 王五  | 销售       | 60000  |

departments(部门表):
| id | name | manager |
|----|------|---------|
| 1  | 销售 | 赵经理  |
| 2  | 工程 | 钱经理  |
| 3  | 市场 | 孙经理  |

注意:市场部没有员工。这个差异是理解三种 JOIN 的关键。

INNER JOIN(内连接)

sql 复制代码
SELECT e.name, d.manager
FROM employees e
INNER JOIN departments d ON e.department = d.name;
lua 复制代码
| name | manager |
|------|---------|
| 张三 | 赵经理  |
| 李四 | 钱经理  |
| 王五 | 赵经理  |

只返回两边都能匹配上的。 市场部没有员工,所以孙经理不会出现。

面试记忆法:INNER JOIN = 交集,只取两边都有的。

LEFT JOIN(左连接)

sql 复制代码
SELECT e.name, d.manager
FROM employees e
LEFT JOIN departments d ON e.department = d.name;
lua 复制代码
| name | manager |
|------|---------|
| 张三 | 赵经理  |
| 李四 | 钱经理  |
| 王五 | 赵经理  |

这个例子里结果跟 INNER JOIN 一样,因为所有员工都能匹配到部门。但如果有个员工的 department 字段是"财务部",而 departments 表里没有"财务部",LEFT JOIN 会保留这个员工,manager 显示 NULL。

面试记忆法:LEFT JOIN 以左表为主,左表一条不少。

RIGHT JOIN(右连接)

sql 复制代码
SELECT e.name, d.manager
FROM employees e
RIGHT JOIN departments d ON e.department = d.name;
sql 复制代码
| name | manager |
|------|---------|
| 张三 | 赵经理  |
| 李四 | 钱经理  |
| 王五 | 赵经理  |
| NULL | 孙经理  |

市场部没有员工,但 RIGHT JOIN 以右表(departments)为主,所以孙经理会保留,name 显示 NULL。

面试记忆法:RIGHT JOIN 以右表为主,右表一条不少。

顺便提一句:SQLite 不支持 RIGHT JOIN,MySQL 和 PostgreSQL 支持。实际开发中很少用 RIGHT JOIN,一般把表顺序换一下用 LEFT JOIN 就行。

面试速查表

连接类型 保留规则 面试怎么答
INNER JOIN 只保留两边都能匹配的 "取交集"
LEFT JOIN 左表全保留,右表补 NULL "以左表为主"
RIGHT JOIN 右表全保留,左表补 NULL "以右表为主"

GROUP BY 聚合

sql 复制代码
SELECT department, COUNT(*) AS 人数, AVG(salary) AS 平均工资
FROM employees
GROUP BY department;
lua 复制代码
| department | 人数 | 平均工资 |
|------------|------|---------|
| 工程       | 1    | 75000   |
| 销售       | 2    | 55000   |

聚合函数:COUNT()SUM()AVG()MAX()MIN()。这几个面试必问,记住就行。


二、Text2SQL 原理:面试怎么答

面试官问"Text2SQL 怎么做",其实就三步:

  1. 提取数据库 Schema(表结构)
  2. 构造提示词,把 Schema 和用户问题一起发给大模型
  3. 执行大模型返回的 SQL,返回结果
sql 复制代码
用户输入:"工程部门员工的姓名和工资是多少?"
    ↓
提示词 = Schema + 用户问题
    ↓
大模型(DeepSeek / ChatGPT / Claude)
    ↓
输出 SQL:SELECT name, salary FROM employees WHERE department = '工程';
    ↓
执行 SQL → 返回结果

面试的时候把这个流程说清楚,再加一句"核心是提示词工程",基本就过了。


三、动手实现

面试过了还不够,自己得能写出来。下面是我实现的完整代码。

环境

bash 复制代码
pip install openai

DeepSeek 兼容 OpenAI 接口,所以直接用 openai 库。

python 复制代码
import os
import sqlite3
from openai import OpenAI

client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com/v1"
)

建库

python 复制代码
conn = sqlite3.connect("text.db")
cursor = conn.cursor()

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

sample_data = [
    (1, "张三", "销售", 50000),
    (2, "李四", "工程", 75000),
    (3, "王五", "销售", 60000),
    (4, "赵六", "工程", 80000),
    (5, "钱七", "市场", 55000)
]
cursor.executemany("INSERT INTO employees VALUES(?,?,?,?)", sample_data)
conn.commit()

提取 Schema

python 复制代码
def get_schema(table_name):
    schema = cursor.execute(f"PRAGMA table_info({table_name})").fetchall()
    return f"CREATE TABLE {table_name} (\n" + \
           "\n".join([f"  {col[1]} {col[2]}" for col in schema]) + "\n);"

这个函数做的事情就是把表结构拼成 CREATE TABLE ... 的格式。面试时可以说"用 PRAGMA table_info 动态提取表结构"。

调用大模型

python 复制代码
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=2048,
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

提示词设计要点:

  1. 给 Schema ------ 不给 Schema 的话 AI 根本不知道你数据库长什么样
  2. 约束输出格式 ------ "只输出 SQL,不要 markdown",不然 AI 返回 sql ... 直接报错
  3. 问题要具体 ------ "查销售部门工资大于5万的员工" 比 "查点数据" 靠谱得多

安全执行

python 复制代码
def safe_execute(sql):
    dangerous = ["DROP", "TRUNCATE", "ALTER"]
    if any(kw in sql.upper() for kw in dangerous):
        raise ValueError(f"危险操作被拦截: {sql}")
    try:
        return cursor.execute(sql).fetchall()
    except Exception as e:
        return f"SQL 执行错误: {e}"

面试时如果被问到"AI 生成的 SQL 安全吗",就说"加了关键词拦截,生产环境建议做白名单,只允许 SELECT"。

跑一下

python 复制代码
question = "工程部门员工的姓名和工资是多少?"
sql_query = ask_deepseek(question, get_schema("employees"))
print(f"AI 生成的 SQL: {sql_query}")

results = safe_execute(sql_query)
for row in results:
    print(row)

# AI 生成的 SQL: SELECT name, salary FROM employees WHERE department = '工程';
# ('李四', 75000)
# ('赵六', 80000)

四、多表 JOIN:面试官最爱追问的

单表查询太简单了,面试官肯定会追问"多表怎么处理"。

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()
python 复制代码
# 把两张表的 Schema 都告诉 AI
def get_full_schema(table_names):
    schema_str = ""
    for table in table_names:
        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"
    return schema_str

schema_str = get_full_schema(["employees", "departments"])

问一个需要 JOIN 的问题:

python 复制代码
question = "列出每个部门的员工人数和平均工资,没有员工的部门也要显示"
sql_query = ask_deepseek(question, schema_str)
print(sql_query)

AI 生成的 SQL:

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.id, d.name;

结果:

sql 复制代码
| department | employee_count | average_salary |
|------------|---------------|----------------|
| 销售       | 2             | 55000          |
| 工程       | 2             | 77500          |
| 市场       | 0             | NULL           |

面试官问"AI 怎么知道用 LEFT JOIN"------因为提示词里说了"没有员工的部门也要显示",AI 理解了这个语义。


五、踩过的坑

坑 1:AI 返回 markdown 格式

go 复制代码
```sql
SELECT name FROM employees;
```

直接 cursor.execute() 报错。解决:提示词里加"不要使用任何 markdown 格式"。

坑 2:中文匹配失败

问"销售部门的员工",AI 生成 WHERE department = '销售',结果返回空。后来发现是 SQLite 中文编码的问题,建库时统一 UTF-8 就好了。

坑 3:AI 搞错表关系

两张表没有外键约束,AI 错误地用了 ON e.id = d.id。解决:在 Schema 里加注释说明关联关系:

python 复制代码
schema_str = """
CREATE TABLE employees (
    id INTEGER,
    name TEXT,
    department TEXT,  -- 关联 departments.name
    salary INTEGER
);
CREATE TABLE departments (
    id INTEGER,
    name TEXT,
    manager TEXT
);
"""

加注释真的有用。 测了几次,加了注释之后 AI 生成的 JOIN 条件基本都对了。


六、进阶:提示词工程技巧

Few-shot 示例

在提示词里给几个例子,AI 输出会稳定很多。面试时可以说"用 Few-shot 提升生成准确率":

python 复制代码
prompt = f"""
你是一个 SQL 专家。请根据以下 Schema 和示例,生成正确的 SQL。

## Schema
{schema}

## 示例
问题:工资最高的员工是谁?
SQL:SELECT name FROM employees ORDER BY salary DESC LIMIT 1;

问题:每个部门有多少人?
SQL:SELECT department, COUNT(*) FROM employees GROUP BY department;

## 规则
1. 只输出 SQL,不要任何解释
2. 优先使用 LEFT JOIN 确保数据不丢失

## 问题
{query}
"""

多轮对话

还可以做上下文记忆,支持追问:

python 复制代码
messages = [
    {"role": "system", "content": f"你是 SQL 专家。数据库 Schema:\n{schema}"}
]

def chat(query):
    messages.append({"role": "user", "content": query})
    response = client.chat.completions.create(
        model="deepseek-v4-flash",
        messages=messages
    )
    answer = response.choices[0].message.content
    messages.append({"role": "assistant", "content": answer})
    return answer

chat("列出所有员工")
chat("只要工程部门的")      # 自动加 WHERE
chat("按工资从高到低排序")   # 自动加 ORDER BY

七、方案对比

面试官可能会问"为什么不直接用现成的框架":

方案 优点 缺点
自己写提示词(本文) 灵活、可控、学习原理 需要手动维护 Schema
LangChain SQL Agent 开箱即用 黑盒、依赖重
vanna.ai 专用框架、支持微调 学习成本高
Chat2DB 有 UI 界面 定制性差

面试时可以说:"学习阶段自己写理解原理,生产环境可以考虑 LangChain 或 vanna.ai。"


八、局限性(面试加分项)

面试时主动说出局限性,会加分:

  • 复杂查询:多层嵌套子查询、窗口函数,AI 容易出错
  • 歧义问题:"最近的订单"------最近是 7 天还是 30 天?AI 不知道
  • 安全性:生成的 SQL 可能有注入风险
  • 性能:AI 生成的 SQL 不一定是最优执行计划

最佳实践

  1. Schema 加注释
  2. 提示词加 Few-shot 示例
  3. 执行前用 EXPLAIN 检查
  4. 权限控制:只允许 SELECT

总结

环节 技术方案
数据库 SQLite
大模型 DeepSeek API
核心技术 提示词工程 + Schema 注入
支持操作 SELECT / INSERT / UPDATE / DELETE / JOIN

做完这个 demo 之后,我再回答面试官那个问题就顺畅多了。核心就是把 Schema 和用户问题一起发给大模型,剩下的就是提示词工程的细节了。

代码不多,核心逻辑几十行,感兴趣的同学可以自己跑一下。有问题评论区聊。

相关推荐
2601_962295581 小时前
python+selenium实现自动化测试
自动化测试·python·selenium·学习心得·web端测试
青 春 记 忆1 小时前
零基础入门python66:FastAPI AI标题、摘要和标签
python·fastapi·后端开发
qq_22589174661 小时前
基于Python+Django的LangGraph智能旅游规划系统
python·django·旅游
青 春 记 忆2 小时前
零基础入门python65:FastAPI 安全调用大模型API
python·fastapi·后端开发
susplus2 小时前
【linux应用软件编程】数据库sqlite3
linux·数据库·sqlite
维克兜率天3 小时前
【维克】特征归一化与标准化:为什么模型对数据的“尺度“很敏感?
人工智能·笔记·python·机器学习·量化
2601_962298673 小时前
Python multiprocessing PicklingError: Can't pickle &l
python·module·multiprocessing·function·picklingerror
CV山月3 小时前
大模型强化学习对齐:从 RLHF 框架到 PPO 算法原理
人工智能·python·大模型·强化学习·多模态·研究生
用户739548002063 小时前
本地 CSV 清洗的小细节:先标准化,再去重,并保留可检查的报告
python