自然语言转SQL
业务人员通过自然语言查询数据库,系统自动生成并执行SQL,返回可视化结果。
项目概述
数据分析是企业决策的核心环节,但大多数业务人员缺乏 SQL 技能,每次查询都需要依赖数据工程师,排期长、沟通成本高。
本案例基于 Vanna.ai 框架搭建了一套 NL2SQL 系统,业务人员用自然语言提问,系统自动生成 SQL 并执行查询,结果以可视化图表呈现。系统还具备"从错误中学习"的能力——当生成的 SQL 出错时,人工修正后系统会自动学习,下次类似查询准确率提升。
关键指标
系统架构
系统采用 Vanna.ai 的核心思路:通过 RAG 架构将数据库元信息(表结构、字段含义、示例查询)作为上下文注入 LLM,实现"上下文增强的 NL2SQL"。
┌──────────────────────────────────────────────────────┐ │ 用户界面 (Streamlit) │ │ ┌──────────────────────────────────────────────┐ │ │ │ 输入: "上个月哪个产品线销售额最高?" │ │ │ └───────────────────┬──────────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ Vanna.ai NL2SQL 引擎 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ 语义理解 │→│ 元数据检索 │→│ SQL生成 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌────────────────────────────────────────┐ │ │ │ │ │ 向量库:表结构 + 示例查询 + DDL │ │ │ │ │ └────────────────────────────────────────┘ │ │ │ └───────────────────┬──────────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────────┐ │ │ │ SQL执行 + 结果可视化 (Plotly) │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 训练闭环:人工修正 → 向量库更新 → 持续学习 │ │ │ └──────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘
实现细节
训练数据准备
DDL 导入
自动扫描数据库获取所有表的 DDL(CREATE TABLE 语句),提取表名、字段名、数据类型、主外键关系、索引等元信息。
文档注入
为每个字段添加业务含义描述,如 `order_amount` → "订单金额(单位:元,含税)"。这些描述向量化后作为检索上下文。
示例查询
整理 30-50 个典型的 Q&A 对("问题-SQL"),覆盖常见的查询模式:聚合、分组、时间过滤、多表JOIN。以 few-shot 形式存入向量库。
查询生成流程
问题理解
LLM 对用户问题做初步解析,提取关键信息:目标实体(销售额、订单量等)、约束条件(时间范围、类别过滤)、聚合方式(总计、平均值、趋势)。
上下文检索
从向量库中检索与问题语义最相似的 DDL 片段、字段描述和示例查询。Top-5 结果作为上下文注入到 LLM 的 prompt 中。
SQL 生成与执行
LLM 根据上下文 + 问题生成 SQL。系统自动做安全校验(是否只读、是否有危险操作),通过后执行并返回结果。结果自动用 Plotly 生成图表。
错误学习与持续优化
错误检测
自动捕获 SQL 执行错误(语法错误、字段不存在、类型不匹配),并分析错误原因。如果是 LLM 的问题,进入修正流程。
人工修正闭环
当生成的 SQL 不正确时,业务人员可在界面上直接修改 SQL。修正后的正确 SQL 会自动加入训练示例库,下次遇到类似查询准确率提升。
查询日志分析
定期分析查询日志,发现高频问题模式(如"用户总是问本月数据但忘记指定时间范围"),更新 prompt 模板和训练数据。
Vanna.ai 核心实现
import vanna
import streamlit as st
from vanna.openai import OpenAI_Chat
from vanna.chromadb import ChromaDB_VectorStore
class MyVanna(ChromaDB_VectorStore, OpenAI_Chat):
def __init__(self, config):
ChromaDB_VectorStore.__init__(self, config=config)
OpenAI_Chat.__init__(self, config=config)
# 初始化
vn = MyVanna(config={
"api_key": os.getenv("OPENAI_API_KEY"),
"model": "gpt-4o"
})
# 1. 训练:导入数据库 schema
vn.train(ddl="""
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
customer_id BIGINT REFERENCES customers(id),
product_id BIGINT REFERENCES products(id),
order_amount DECIMAL(10,2) COMMENT '订单金额,单位元',
order_date DATE COMMENT '订单日期',
status VARCHAR(20) COMMENT '订单状态'
);
""")
# 2. 训练:注入业务文档
vn.train(documentation="order_amount字段单位为人民币元,含税")
vn.train(documentation="status字段取值:pending/paid/shipped/delivered/cancelled")
# 3. 训练:添加示例查询
vn.train(question="上个月的总销售额是多少?",
sql="SELECT SUM(order_amount) FROM orders "
"WHERE order_date >= date_trunc('month', CURRENT_DATE - INTERVAL '1 month') "
"AND order_date < date_trunc('month', CURRENT_DATE)")
# 4. Streamlit 交互界面
st.title("📊 自然语言查数据")
question = st.text_input("用自然语言描述你要查什么:")
if question:
sql = vn.generate_sql(question)
st.code(sql, language="sql")
# 显示查询结果
df = vn.run_sql(sql)
st.dataframe(df)
# 自动可视化(如果有聚合数据)
fig = vn.get_plotly_figure(question, sql, df)
if fig:
st.plotly_chart(fig)
# 人工修正回路
fix = st.text_area("如果结果不对,可以修改 SQL:")
if fix:
vn.train(question=question, sql=fix)
st.success("已学习!下次类似查询会使用这个修正。")
经验教训
- 字段的业务描述是最重要的投资 — 只导 DDL 准确率只有 60%,加上字段业务描述后提升到 80%+。投入时间写好注释事半功倍
- 复杂 JOIN 是主要失败点 — 3 表以上的 JOIN 查询更容易出错。建议先让用户明确说明涉及哪些表,或者提供可视化的 ER 图辅助
- 安全校验不能依赖 LLM — 即使 GPT-4 也可能生成 DELETE/UPDATE,必须加 SQL 白名单或只读用户
- 学习效应有"数据漂移"问题 — 随着时间推移,旧示例可能不再适用。建议每季度清理低质量训练样本,保持向量库质量