跳到正文

更多文章

影响力日常操作系统:21天习惯养成计划 从技能雇佣者到价值创造者 互惠账户的运营 影响力的三层架构 组织的注意力经济学
Text-to-SQL 自然语言查询实战

本文来源于数据从业者全栈知识库,更多体系化内容请访问知识库。

文档定位

本文写给每天写 SQL 的人——分析师、数据工程师、BI 开发者。不是 AI 科普,是把这件事从原理到工具链到实际用法讲清楚,帮你判断哪些场景值得用、哪些场景还是自己写更稳。

目录

  • #是什么,为什么现在才真正可用
  • #主流工具全景(2025年实际在用的)
  • #核心技术原理
  • #工程实践:一个完整的实现
  • #数据分析师的日常用法
  • #对数据岗位的真实影响
  • #总结

是什么,为什么现在才真正可用

基本原理

Text-to-SQL 做的事情直白说就一件:把人话翻译成 SQL。

用户输入:上个月 GMV 最高的品类是哪几个?
[理解意图 + 理解表结构]
SELECT category, SUM(gmv) AS total_gmv
FROM orders
WHERE order_date >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month')
AND order_date < DATE_TRUNC('month', CURRENT_DATE)
GROUP BY category
ORDER BY total_gmv DESC
LIMIT 10;

这个转换过程拆开来有三个核心子任务:

  1. 意图理解:判断用户要做什么(查询、聚合、排名、趋势…)
  2. Schema Linking:把自然语言里的实体映射到具体的表名、字段名
  3. SQL 生成:按数据库方言(MySQL / Hive / Snowflake / BigQuery…)生成语法正确的 SQL

前两步一直是难点。意图理解还好做,Schema Linking 才是核心瓶颈——“GMV”对应的是哪张表的哪个字段?“品类”是 category 还是 category_name 还是 item_type?没有对业务的理解,这一步就是猜。

为什么 LLM 让这件事真正可用了

在 LLM 之前,Text-to-SQL 也有各种学术解法,准确率在标准数据集(Spider、WikiSQL)上看起来不错,但工业落地一塌糊涂。核心原因是旧方案的两个硬伤:

  • Schema 理解靠模板:只能处理训练过的固定 schema,遇到新表就不认识
  • 上下文窗口极小:无法在推理时注入完整的表结构信息

LLM 解决了这两个问题:

能力旧方案的局限LLM 的改变
Schema 理解依赖训练数据里出现过的表结构长上下文窗口可以直接把 DDL 塞进 prompt
语义泛化新业务词汇需要重新标注训练预训练的语言理解能力覆盖大多数业务场景
方言适配每种数据库需要单独适配理解 “Hive SQL” “BigQuery SQL” 等自然语言描述
多轮对话基本不支持可以追问澄清,逐步修正

这才是 GPT-4、Claude 这一代模型真正改变 Text-to-SQL 的地方——不是更聪明的 SQL 语法生成,而是让 schema 理解这件事变得动态、灵活

局限性:必须诚实说的部分

以下场景 AI 生成的 SQL 不可信

不是说 AI 不好,是这些场景有结构性难点,用之前要明确预期。

1. 复杂多表 JOIN

涉及 5 张表以上、JOIN 条件有业务逻辑(比如”只取最新版本的记录”)时,LLM 容易写出语法正确但语义错误的 SQL。这种错误很隐蔽,结果看起来有数但是不对。

2. 业务术语映射

“活跃用户”在你们公司是 30 日 DAU 还是 7 日 DAU?“GMV”含不含退款?这些业务定义 AI 不知道,它只能猜或者套通用定义。

3. 幻觉问题

AI 可能生成引用了不存在字段的 SQL,或者 JOIN 了根本没有关联关系的表。这在 schema 较大(几百张表)时尤其常见。

4. 性能意识缺失

AI 生成的 SQL 大多能跑出结果,但经常不考虑分区裁剪、索引利用、物化视图等性能因素。在大数据量场景下直接用可能扫全表。


主流工具全景(2025年实际在用的)

这里按使用场景分类,不按厂商分类——因为你选工具的第一个问题是”我在哪里用”,不是”哪个产品更好”。

工具对比总览

类别工具适用场景接入成本准确率特点
AI IDECursor开发环境写 SQL极低,换个 IDE依赖上下文质量,需要手动粘贴 schema
AI IDEGitHub Copilot已有 VS Code 习惯的团队偏代码补全,SQL 感知弱于 Cursor
专门工具Vanna.ai中小团队快速部署中,需要接数据库支持 RAG 训练,准确率可随业务定制
专门工具DAIL-SQL学术/科研团队高,需要本地部署在 Spider 基准上表现最优之一
平台内置Databricks AI Query已在 Databricks 的团队极低,开箱即用对 Unity Catalog 元数据感知好
平台内置Snowflake Cortex AnalystSnowflake 用户极低,开箱即用支持语义模型定义,准确率有保障
平台内置BigQuery 自然语言查询GCP 用户极低,开箱即用与 Gemini 深度集成
自建方案LangChain + 自有 LLM有定制需求、数据安全要求高高,需要工程投入上限高,下限取决于实现质量

各类工具详解

AI IDE(Cursor / Copilot)

本质是把 LLM 嵌入开发环境,没有专门的 Text-to-SQL 引擎。优势是零接入成本,劣势是 schema 感知靠你手动提供。

实际用法:在 Cursor 里打开 SQL 文件,@引用 schema 文件或者直接粘贴 DDL,然后用自然语言描述需求。适合个人效率提升,不适合团队标准化部署。

Vanna.ai

目前开源生态里最实用的 Text-to-SQL 框架之一。核心机制是:

  • 连接你的数据库,自动提取 schema
  • 支持”训练”——往里喂你自己写过的 SQL 和对应的业务问题
  • 查询时用 RAG 从历史 SQL 里检索相关示例,作为 few-shot

这是它和纯 prompt 方案最大的区别:准确率会随使用积累而提升

平台内置工具(Snowflake Cortex Analyst 为例)

Snowflake Cortex Analyst 的设计思路值得关注:它引入了”语义模型层”,让数据团队用 YAML 定义业务术语和字段的映射关系,然后 AI 查询基于这个语义层,而不是直接理解原始 schema。

这个设计解决了”业务术语映射”的核心难题,但代价是需要维护语义模型——本质上是把人工工作从”写 SQL”转移到了”维护语义层”。

选型建议
  • 个人提效:直接用 Cursor,成本最低
  • 团队内部工具:Vanna.ai 或平台内置(如果已在 Databricks/Snowflake 生态)
  • 有数据安全要求(金融、医疗):自建方案 + 私有化部署 LLM
  • 追求最高准确率的科研场景:DAIL-SQL

核心技术原理

这部分面向想自己动手实现或深度定制的读者。

Schema Linking:让 LLM 理解你的表结构

Schema Linking 是 Text-to-SQL 的核心难题。原始的 schema 信息可能有几百张表、几千个字段,全部塞进 prompt 会超出上下文窗口,而且会引入噪声。

主流方案是分两步走

第一步:Schema 检索(过滤不相关的表)
用户问题 → 向量化 → 在表/字段的向量索引中检索 → 筛选 Top-K 相关表
第二步:Schema 注入(格式化后送给 LLM)
相关表的 DDL + 字段注释 + 数据样例 → 结构化 prompt

给 LLM 的 schema 信息格式,实测下来 带注释的 DDL 效果最好:

-- 订单主表,记录所有交易订单
CREATE TABLE orders (
order_id BIGINT COMMENT '订单ID,全局唯一',
user_id BIGINT COMMENT '用户ID,关联users表',
category VARCHAR(50) COMMENT '商品品类,如:服饰、3C、食品',
gmv DECIMAL(18,2) COMMENT '订单金额,含税,不含退款',
order_date DATE COMMENT '下单日期',
status VARCHAR(20) COMMENT '订单状态:paid/refunded/cancelled'
);

注释里写清楚字段的业务含义,比列出 10 张表的原始 DDL 效果好得多。

上下文注入策略

一个完整的 Text-to-SQL prompt 通常包含以下部分,顺序很重要:

[系统角色]
你是一个数据分析助手,帮助将自然语言问题转换为 SQL 查询。
[数据库信息]
数据库类型:Hive SQL(Spark 兼容)
数据库名:dw_retail
[相关表结构]
<这里是筛选后的 DDL + 注释>
[业务规则](可选,高价值)
- "活跃用户"定义:近 30 天有下单行为的用户
- GMV 统计口径:paid 状态的订单,不含退款
- 分区字段 dt 必须在 WHERE 条件中出现
[历史示例](Few-shot)
问题:上周各品类的销售额?
SQL:SELECT category, SUM(gmv) ... WHERE dt BETWEEN ...
[当前问题]
用户问题:<用户输入>
[输出要求]
只输出 SQL,不需要解释。SQL 末尾加分号。

业务规则这一块是最容易被忽略、但效果提升最明显的地方。 把团队约定俗成的 SQL 规范、指标定义、分区使用规范写进系统 prompt,能直接干掉一大类的准确率问题。

Few-shot Examples 的作用

给 LLM 几个”问题 → SQL”的示例,比解释规则有效得多。原因很简单:示例直接展示了你期望的 SQL 风格(命名习惯、缩进格式、JOIN 方式)。

关键是示例的相关性,不是数量。3 个高度相关的示例,比 20 个通用示例效果好。

这就引出了 RAG 的用法。

RAG 在 Text-to-SQL 中的应用

参见 09-RAG检索增强生成实战 的基本原理,这里说 SQL 场景的具体做法:

把你们团队历史积累的”经典 SQL”作为知识库,每条记录包含:

  • 业务问题描述(作为检索的锚点)
  • 对应的 SQL
  • 可选:适用场景标注

查询时,先用用户问题去检索相关的历史 SQL,把 Top-3 作为 few-shot examples 注入 prompt。

# 示意结构
历史 SQL= [
{
"question": "上个月各渠道的新增用户数",
"sql": "SELECT channel, COUNT(DISTINCT user_id) ...",
"tags": ["用户增长", "渠道分析"]
},
...
]
# 查询时
相关示例 = vector_search(用户问题, 历史SQL库, top_k=3)
prompt = build_prompt(schema, 相关示例, 用户问题)

这是 Vanna.ai 的核心机制,也是自建方案里效果提升最明显的一步。详细的 RAG 工程实现可以参考 26-向量数据库与RAG工程实践。


工程实践:一个完整的实现

用 Python + Anthropic API 实现一个可运行的 Text-to-SQL 原型。不依赖 LangChain,逻辑更清晰。

环境准备

Terminal window
pip install anthropic sqlalchemy duckdb

完整代码

"""
Text-to-SQL 原型实现
使用 Claude API + DuckDB(可替换为任意数据库)
"""
import anthropic
import duckdb
import json
from typing import Optional
# ============================================================
# 1. 数据库连接和 Schema 提取
# ============================================================
def get_schema_ddl(conn: duckdb.DuckDBPyConnection) -> str:
"""从数据库中提取所有表的 DDL 信息"""
tables = conn.execute("SHOW TABLES").fetchall()
ddl_parts = []
for (table_name,) in tables:
# 获取建表语句
create_sql = conn.execute(
f"SELECT sql FROM sqlite_master WHERE name='{table_name}'"
).fetchone()
# 获取字段信息
columns = conn.execute(f"DESCRIBE {table_name}").fetchall()
col_info = "\n".join(
f" -- {col[0]}: {col[1]}" for col in columns
)
ddl_parts.append(f"-- 表: {table_name}\n{col_info}")
return "\n\n".join(ddl_parts)
def get_sample_data(conn: duckdb.DuckDBPyConnection, table_name: str, n: int = 3) -> str:
"""获取表的样例数据,帮助 LLM 理解数据格式"""
try:
rows = conn.execute(f"SELECT * FROM {table_name} LIMIT {n}").fetchdf()
return rows.to_string(index=False)
except Exception:
return ""
# ============================================================
# 2. Prompt 构建
# ============================================================
SYSTEM_PROMPT = """你是一个数据查询助手,将用户的自然语言问题转换为 SQL 查询。
规则:
1. 只输出 SQL,不要解释,不要 markdown 代码块
2. SQL 末尾加分号
3. 使用 DuckDB SQL 语法
4. 日期处理使用 CURRENT_DATE
5. 字符串比较使用单引号
6. 如果问题不明确,生成最合理的解释对应的 SQL
"""
def build_prompt(
schema_info: str,
question: str,
few_shot_examples: Optional[list] = None,
business_rules: Optional[str] = None
) -> str:
"""构建发送给 LLM 的 prompt"""
parts = []
# Schema 信息
parts.append(f"## 数据库 Schema\n\n{schema_info}")
# 业务规则(如果有)
if business_rules:
parts.append(f"## 业务规则\n\n{business_rules}")
# Few-shot 示例(如果有)
if few_shot_examples:
examples_text = "\n\n".join(
f"问题:{ex['question']}\nSQL:{ex['sql']}"
for ex in few_shot_examples
)
parts.append(f"## 参考示例\n\n{examples_text}")
# 当前问题
parts.append(f"## 当前问题\n\n{question}")
return "\n\n---\n\n".join(parts)
# ============================================================
# 3. LLM 调用
# ============================================================
def generate_sql(
client: anthropic.Anthropic,
prompt: str,
model: str = "claude-opus-4-5"
) -> str:
"""调用 Claude API 生成 SQL"""
message = client.messages.create(
model=model,
max_tokens=1024,
system=SYSTEM_PROMPT,
messages=[
{"role": "user", "content": prompt}
]
)
return message.content[0].text.strip()
# ============================================================
# 4. SQL 执行与结果返回
# ============================================================
def execute_sql(conn: duckdb.DuckDBPyConnection, sql: str) -> dict:
"""执行 SQL 并返回结果"""
try:
result_df = conn.execute(sql).fetchdf()
return {
"success": True,
"sql": sql,
"rows": len(result_df),
"data": result_df.to_dict(orient="records"),
"columns": list(result_df.columns)
}
except Exception as e:
return {
"success": False,
"sql": sql,
"error": str(e)
}
# ============================================================
# 5. 完整的 Text-to-SQL Pipeline
# ============================================================
def text_to_sql_query(
question: str,
conn: duckdb.DuckDBPyConnection,
client: anthropic.Anthropic,
few_shot_examples: Optional[list] = None,
business_rules: Optional[str] = None,
auto_execute: bool = True
) -> dict:
"""
完整的 Text-to-SQL 流程
Args:
question: 自然语言问题
conn: 数据库连接
client: Anthropic 客户端
few_shot_examples: 历史示例列表
business_rules: 业务规则描述
auto_execute: 是否自动执行生成的 SQL
Returns:
包含 SQL 和查询结果的字典
"""
# Step 1: 提取 Schema
schema_info = get_schema_ddl(conn)
# Step 2: 构建 Prompt
prompt = build_prompt(
schema_info=schema_info,
question=question,
few_shot_examples=few_shot_examples,
business_rules=business_rules
)
# Step 3: 生成 SQL
generated_sql = generate_sql(client, prompt)
print(f"[生成的 SQL]\n{generated_sql}\n")
# Step 4: 执行(可选)
if auto_execute:
result = execute_sql(conn, generated_sql)
return result
else:
return {"sql": generated_sql, "executed": False}
# ============================================================
# 6. 使用示例
# ============================================================
if __name__ == "__main__":
# 初始化数据库(使用 DuckDB 内存数据库做演示)
conn = duckdb.connect(":memory:")
# 创建示例表
conn.execute("""
CREATE TABLE orders (
order_id INTEGER,
user_id INTEGER,
category VARCHAR,
gmv DECIMAL(10,2),
order_date DATE,
status VARCHAR
)
""")
# 插入测试数据
conn.execute("""
INSERT INTO orders VALUES
(1, 101, '服饰', 299.00, '2026-02-01', 'paid'),
(2, 102, '3C', 1299.00, '2026-02-05', 'paid'),
(3, 101, '食品', 88.00, '2026-02-10', 'refunded'),
(4, 103, '服饰', 459.00, '2026-02-15', 'paid'),
(5, 104, '3C', 3999.00, '2026-02-20', 'paid')
""")
# 初始化 Anthropic 客户端
client = anthropic.Anthropic() # 从环境变量读取 ANTHROPIC_API_KEY
# 定义业务规则
business_rules = """
- GMV 统计只包含 status='paid' 的订单,不含 refunded
- 品类字段 category 的有效值:服饰、3C、食品、家居、美妆
"""
# 定义 Few-shot 示例
examples = [
{
"question": "各品类的订单数量",
"sql": "SELECT category, COUNT(*) AS order_count FROM orders WHERE status='paid' GROUP BY category ORDER BY order_count DESC;"
}
]
# 执行查询
result = text_to_sql_query(
question="上个月 GMV 最高的品类是哪些?",
conn=conn,
client=client,
few_shot_examples=examples,
business_rules=business_rules
)
if result["success"]:
print(f"查询成功,返回 {result['rows']} 行数据")
print(json.dumps(result["data"], ensure_ascii=False, indent=2))
else:
print(f"执行失败:{result['error']}")

关键设计决策说明

为什么不用 LangChain 的 SQLDatabaseChain?

LangChain 封装得太重,出问题时很难调试。自己实现的好处是每一步都透明:prompt 是什么、LLM 返回了什么、执行出了什么错。在生产环境里,可观测性比简洁代码更重要。

auto_execute 参数的意义

生产环境建议 auto_execute=False,先让人工审核 SQL 再执行,尤其是涉及大表的查询。如果你们数仓是按扫描量计费的(BigQuery、Snowflake),一个没有分区过滤的 AI SQL 可能让你意外账单翻倍。

接入生产数据库的注意事项
  • 给 Text-to-SQL 使用的数据库账号设置只读权限
  • 对生成的 SQL 做基本的安全检查(拦截 DROP、DELETE、UPDATE 等危险操作)
  • 记录每次生成的 SQL 和执行结果,方便审计和改进

数据分析师的日常用法

这部分专门写给不做工程开发、主要用 SQL 做分析的人。

用 Cursor 提升日常取数效率

最实用的工作流

  1. 在项目里建一个 schema/ 目录,把常用的表 DDL 存成 .sql 文件
  2. 打开 Cursor,在 Chat 里 @schema/orders.sql @schema/users.sql
  3. 直接用中文描述需求

这比每次去查表结构快很多。关键是建好自己的 schema 文件库,这是一次性投入、长期收益。

一个实际的对话示例

你:@schema/orders.sql @schema/users.sql
帮我写一个查询:找出近 90 天内,购买过 3C 品类但从未购买过服饰品类的用户,
按他们的 3C 消费总额降序排列,取前 100 名。
Claude:[生成 SQL]
你:把时间条件改成近 30 天,另外加上用户的注册时间
Claude:[修改后的 SQL]

多轮修改是 Cursor 的优势所在——不用每次重新描述上下文。

什么场景下 AI 生成的 SQL 必须人工 Review

以下情况必须仔细核对,不能直接用

1. 涉及指标定义的查询

“活跃用户”、“GMV”、“转化率”这类词,每家公司定义不一样,AI 会用它认为”合理”的定义。你需要对照公司指标文档确认口径。

2. 多表 JOIN 超过 3 张

检查每个 JOIN 的连接条件是否符合业务逻辑,特别注意:

  • 一对多关系有没有导致数据膨胀
  • 有没有漏掉必要的过滤条件(比如只取最新状态)

3. 涉及去重(DISTINCT)的聚合

COUNT(DISTINCT user_id) 这类写法,AI 不一定在正确的粒度上做去重。

4. 用在报表或对外汇报的数据

自己用来探索性分析,可以容忍小误差。但要放到报表里、发给业务方或汇报给 leader 的数据,必须人工验证。

验证方法:对结果数据做基本的合理性检查——量级是否符合预期、极值是否异常、和已知数据对照。

如何建设团队的 Text-to-SQL 能力

如果你想在团队层面推广 Text-to-SQL 工具(比如接入 Vanna.ai 或者内部 BI 工具),业务术语库建设是核心工作,不是工具接入。

建术语库的方法

# business_glossary.yaml 示例结构
metrics:
gmv:
definition: 成交总额
sql_expression: "SUM(CASE WHEN status='paid' THEN amount ELSE 0 END)"
table: orders
note: 不含退款,不含运费
dau:
definition: 日活跃用户数
sql_expression: "COUNT(DISTINCT user_id)"
table: user_events
filter: "event_date = CURRENT_DATE"
note: 有任意行为即算活跃,包括浏览
dimensions:
channel:
field: acquisition_channel
table: users
values: [organic, paid_search, social, referral]
category:
field: first_category
table: items
note: 使用一级品类,不是二级品类

这个术语库可以直接转换成 LLM 的系统 prompt,也可以作为 Vanna.ai 的训练数据。维护术语库的工作,本质上和维护数据字典是一件事,只是输出格式不同。


对数据岗位的真实影响

这个话题有很多过度乐观或过度悲观的预测,我只说能观察到的事实。

变得更重要的技能

SQL 性能优化和调试能力

AI 能写出跑得动的 SQL,但不一定是高效的 SQL。在大规模数据集上,一个没有分区过滤的查询和一个优化过的查询,资源消耗可能差 100 倍。理解执行计划、索引机制、分区策略,依然是核心竞争力。参见 06-大语言模型全景解析 关于 LLM 能力边界的讨论——工具不懂”贵”,人要懂。

数据理解和业务 sense

你能判断 AI 生成的 SQL 对不对,前提是你知道业务预期是什么。这种判断力来自对数据和业务的深度理解,AI 替代不了。

Prompt 工程能力

把你的需求准确地描述给 AI——包括说清楚业务规则、指明数据口径、给出约束条件。这不是一个小技巧,是一个需要练习的技能。详见 08-Prompt Engineering提示工程。

数据质量和数据治理

AI 生成 SQL 的质量上限,取决于数据本身的质量和 schema 的规范程度。字段命名混乱、没有注释、业务含义不清的表,AI 查询效果会很差。数据治理工作反而因此变得更重要

降低重要性的技能

记忆 SQL 语法细节

窗口函数的写法、日期函数的参数顺序、各数据库方言的细微差异——这些不需要记忆,AI 处理得比人准确。

编写模板化的取数 SQL

“按某个维度聚合某个指标”这类标准取数,AI 写得又快又准。花时间手写这类 SQL 的 ROI 在下降。

格式化和注释

代码整洁这件事,AI 做得比大多数人好。

结构性判断

Text-to-SQL 工具正在把数据分析师的工作重心从”怎么写 SQL”转向”问什么问题、验证结果是否合理、管理数据质量”。SQL 语言本身的重要性在下降,但数据分析能力的重要性在上升。这对真正懂业务、懂数据的人是利好,对只会机械写 SQL 的人是压力。


总结

Text-to-SQL 现在真的可用了,但”可用”和”可信”之间还有距离。

几个核心判断给你带走:

  • 个人效率场景:直接用 Cursor + 自己整理好的 schema 文件,成本最低,今天就能开始
  • 团队工具场景:核心投入是业务术语库,不是工具选型;Vanna.ai 是目前工程上最实用的开源选择
  • 准确率瓶颈:不在 LLM 的 SQL 生成能力,在 schema 质量和业务术语映射
  • 必须 review 的场景:多表复杂 JOIN、涉及指标定义、对外汇报的数据——不要省这一步
  • 岗位影响:SQL 语法记忆能力在降权,数据理解和业务判断能力在升值

相关延伸阅读:

  • 09-RAG检索增强生成实战 - 理解如何用历史 SQL 提升 Text-to-SQL 准确率
  • 26-向量数据库与RAG工程实践 - RAG 工程层面的向量检索实现
  • 08-Prompt Engineering提示工程 - 系统性提升 prompt 质量
  • 06-大语言模型全景解析 - 理解 LLM 的能力边界,知道哪里该信、哪里该查

#Text-to-SQL #NL2SQL #AI #数据分析 #LLM #数据工程


本文节选自数据从业者全栈知识库。知识库包含 2300+ 篇体系化技术文档,覆盖数据分析、数据工程、数据治理、AI 等全栈领域。了解更多 →

Elazer (石头)
Elazer (石头)

11 年数据老兵,从分析师到架构专家。用真实经历帮数据人少走弯路。

加入免费社群

和数据从业者一起交流成长

了解详情 →

成为会员

解锁全部内容 + 知识库

查看权益 →
← 上一篇 职场认知 32|十年磨一剑:长期主义 vs 机会主义,数据技术专家的成长史 下一篇 → 数据地基(三):最稀缺的能力,不在简历上