本文来源于数据从业者全栈知识库,更多体系化内容请访问知识库。
文档定位本文写给每天写 SQL 的人——分析师、数据工程师、BI 开发者。不是 AI 科普,是把这件事从原理到工具链到实际用法讲清楚,帮你判断哪些场景值得用、哪些场景还是自己写更稳。
目录
- #是什么,为什么现在才真正可用
- #主流工具全景(2025年实际在用的)
- #核心技术原理
- #工程实践:一个完整的实现
- #数据分析师的日常用法
- #对数据岗位的真实影响
- #总结
是什么,为什么现在才真正可用
基本原理
Text-to-SQL 做的事情直白说就一件:把人话翻译成 SQL。
用户输入:上个月 GMV 最高的品类是哪几个? ↓ [理解意图 + 理解表结构] ↓SELECT category, SUM(gmv) AS total_gmvFROM ordersWHERE order_date >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month') AND order_date < DATE_TRUNC('month', CURRENT_DATE)GROUP BY categoryORDER BY total_gmv DESCLIMIT 10;这个转换过程拆开来有三个核心子任务:
- 意图理解:判断用户要做什么(查询、聚合、排名、趋势…)
- Schema Linking:把自然语言里的实体映射到具体的表名、字段名
- 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 IDE | Cursor | 开发环境写 SQL | 极低,换个 IDE | 依赖上下文质量,需要手动粘贴 schema |
| AI IDE | GitHub Copilot | 已有 VS Code 习惯的团队 | 低 | 偏代码补全,SQL 感知弱于 Cursor |
| 专门工具 | Vanna.ai | 中小团队快速部署 | 中,需要接数据库 | 支持 RAG 训练,准确率可随业务定制 |
| 专门工具 | DAIL-SQL | 学术/科研团队 | 高,需要本地部署 | 在 Spider 基准上表现最优之一 |
| 平台内置 | Databricks AI Query | 已在 Databricks 的团队 | 极低,开箱即用 | 对 Unity Catalog 元数据感知好 |
| 平台内置 | Snowflake Cortex Analyst | Snowflake 用户 | 极低,开箱即用 | 支持语义模型定义,准确率有保障 |
| 平台内置 | 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,逻辑更清晰。
环境准备
pip install anthropic sqlalchemy duckdb完整代码
"""Text-to-SQL 原型实现使用 Claude API + DuckDB(可替换为任意数据库)"""
import anthropicimport duckdbimport jsonfrom 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_DATE5. 字符串比较使用单引号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 提升日常取数效率
最实用的工作流:
- 在项目里建一个
schema/目录,把常用的表 DDL 存成.sql文件 - 打开 Cursor,在 Chat 里
@schema/orders.sql @schema/users.sql - 直接用中文描述需求
这比每次去查表结构快很多。关键是建好自己的 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 等全栈领域。了解更多 →