提升 Text2SQL 准确率
摘要
随着大语言模型的爆发,Text2SQL(自然语言转SQL)技术正在重塑我们与数据库的交互方式。本文将系统性地梳理提升 Text2SQL 准确率的核心方法,涵盖提示工程、模型微调、推理增强三大维度。所有示例基于微软 AdventureWorksDW2016 数据仓库。
引言:为什么 Text2SQL 准确率如此重要?
在数据驱动的时代,让非技术用户能够通过自然语言查询数据库,是一个极具价值但也极具挑战的任务。传统的 Text2SQL 系统准确率有限,难以投入实际应用。但随着 GPT-4、Llama、Qwen 等大模型的出现,这一领域迎来了革命性的突破。
目前业界领先的系统在 Spider 数据集上已能达到 82.5% 的执行准确率,但如何在实际业务场景中达到甚至超越这个水平?本文将从五个核心维度展开:
- Schema 设计与呈现 - 让模型更好地理解数据库结构
- 提示工程优化 - 设计高效的提示词策略
- 业务上下文配置 - 消除业务语言与技术语言的鸿沟
- 模型微调方法 - 针对特定任务优化模型能力
- 推理时增强策略 - 在推理阶段提升准确率
本文使用的是微软SQLServer官方的示例库,使用的是数据仓库的示例库AdventureWorksDW,我学习数据仓库的时候一直都参考这个库,大家可以很容易在微软的SQLServer官方网站上下载到这个库。
一、Schema 设计与呈现:打好地基
1.1 J-Schema:更友好的数据库结构呈现
传统 Schema 往往只提供表名和字段名,这对于模型理解业务语义是不够的。J-Schema 方法提出了一种更优秀的呈现方式:
示例:DimCustomer 表(客户维度表)
表名: DimCustomer (客户维度表)
字段:
- CustomerKey (客户代理键, 主键, 自增)
- GeographyKey (地理代理键, 外键 → DimGeography.GeographyKey)
- CustomerAlternateKey (客户业务标识, 如会员编号)
- FirstName (名)
- LastName (姓)
- BirthDate (出生日期, 格式: YYYY-MM-DD)
- MaritalStatus (婚姻状态, 可选值: S-单身/M-已婚)
- Gender (性别, 可选值: M-男/F-女)
- EmailAddress (电子邮箱)
- YearlyIncome (年收入, 单位: 美元)
- TotalChildren (子女总数)
- EnglishEducation (教育程度, 可选值: Bachelors/Graduate/High School/Partial College/Partial High School)
- EnglishOccupation (职业, 如: Professional/Management/Clerical 等)
- AddressLine1 (地址第一行)
- Phone (电话号码)
- DateFirstPurchase (首次购买日期)
- CommuteDistance (通勤距离, 如: 0-1 Miles/1-2 Miles/5-10 Miles)
示例值:
- CustomerKey: 11000, 11001
- FirstName: Jon, Yang
- LastName: Yang, Zhu
- BirthDate: 1972-01-12
- MaritalStatus: S
- Gender: M
- YearlyIncome: 90000.00
- EnglishEducation: Bachelors
- EnglishOccupation: Professional
Key points:
- ✅ 提供字段语义描述
- ✅ 说明主外键关系
- ✅ 给出字段格式和可选值
- ✅ 提供示例数据(帮助模型理解数据特征)
1.2 表关联关系管理
多表查询是 Text2SQL 的难点之一。清晰定义表关联关系,能让模型生成更准确的 JOIN 语句:
AdventureWorksDW2016 核心表关联关系:
json
{
"relationships": [
{
"from_table": "FactInternetSales",
"from_column": "CustomerKey",
"to_table": "DimCustomer",
"to_column": "CustomerKey",
"relation_type": "many_to_one",
"description": "一个客户可以有多个互联网销售订单"
},
{
"from_table": "FactInternetSales",
"from_column": "ProductKey",
"to_table": "DimProduct",
"to_column": "ProductKey",
"relation_type": "many_to_one",
"description": "一个产品可以被多次销售"
},
{
"from_table": "FactInternetSales",
"from_column": "OrderDateKey",
"to_table": "DimDate",
"to_column": "DateKey",
"relation_type": "many_to_one",
"description": "订单日期关联到日期维度"
},
{
"from_table": "DimCustomer",
"from_column": "GeographyKey",
"to_table": "DimGeography",
"to_column": "GeographyKey",
"relation_type": "many_to_one",
"description": "客户关联到地理位置"
},
{
"from_table": "DimProduct",
"from_column": "ProductSubcategoryKey",
"to_table": "DimProductSubcategory",
"to_column": "ProductSubcategoryKey",
"relation_type": "many_to_one",
"description": "产品属于某个子类别"
},
{
"from_table": "DimProductSubcategory",
"from_column": "ProductCategoryKey",
"to_table": "DimProductCategory",
"to_column": "ProductCategoryKey",
"relation_type": "many_to_one",
"description": "子类别属于某个大类"
},
{
"from_table": "FactResellerSales",
"from_column": "ResellerKey",
"to_table": "DimReseller",
"to_column": "ResellerKey",
"relation_type": "many_to_one",
"description": "经销商销售关联到经销商维度"
},
{
"from_table": "FactResellerSales",
"from_column": "EmployeeKey",
"to_table": "DimEmployee",
"to_column": "EmployeeKey",
"relation_type": "many_to_one",
"description": "经销商销售关联到员工(销售代表)"
}
]
}
如果建表脚本已经包含了这些信息,可以让大模型帮助生成这些关联信息。
二、提示工程优化:引导模型思考
2.1 思维链(Chain-of-Thought)引导
复杂查询需要多步推理。通过思维链提示,引导模型逐步分析,以下是一个具体的示例:
示例:查询 2014 年每个产品类别的销售额
问题: 查询2014年每个产品类别的总销售额,按销售额降序排列
思考过程:
1. 确定时间范围: 2014年 → DimDate.CalendarYear = 2014
2. 需要的字段: 产品类别名、销售额总计
3. 涉及的表:
- FactInternetSales (销售事实表)
- DimProduct (产品维度)
- DimProductSubcategory (产品子类别)
- DimProductCategory (产品类别)
- DimDate (日期维度)
4. 关联条件:
- FactInternetSales.ProductKey = DimProduct.ProductKey
- DimProduct.ProductSubcategoryKey = DimProductSubcategory.ProductSubcategoryKey
- DimProductSubcategory.ProductCategoryKey = DimProductCategory.ProductCategoryKey
- FactInternetSales.OrderDateKey = DimDate.DateKey
5. 聚合逻辑: 按 EnglishProductCategoryName 分组, SUM(SalesAmount) 计算总额
6. 排序: ORDER BY 总销售额 DESC
SQL:
SELECT
dc.EnglishProductCategoryName AS ProductCategory,
SUM(fis.SalesAmount) AS TotalSalesAmount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
INNER JOIN DimDate dd ON fis.OrderDateKey = dd.DateKey
WHERE dd.CalendarYear = 2014
GROUP BY dc.EnglishProductCategoryName
ORDER BY TotalSalesAmount DESC;
2.2 Few-Shot 示例库
针对复杂、高频的查询场景,提供示例 SQL:
json
examples = [
{
"question": "查询每个产品类别的销售数量",
"sql": """
SELECT
dc.EnglishProductCategoryName,
COUNT(*) AS SalesCount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
GROUP BY dc.EnglishProductCategoryName
ORDER BY SalesCount DESC;
"""
},
{
"question": "查找最近30天未下单的客户",
"sql": """
SELECT
dc.FirstName,
dc.LastName,
dc.EmailAddress
FROM DimCustomer dc
WHERE dc.CustomerKey NOT IN (
SELECT DISTINCT CustomerKey
FROM FactInternetSales
WHERE OrderDate >= DATEADD(DAY, -30, GETDATE())
);
"""
},
{
"question": "查询每个地区的销售额和订单数",
"sql": """
SELECT
dst.SalesTerritoryRegion,
dst.SalesTerritoryCountry,
COUNT(DISTINCT fis.SalesOrderNumber) AS OrderCount,
SUM(fis.SalesAmount) AS TotalSales
FROM FactInternetSales fis
INNER JOIN DimSalesTerritory dst ON fis.SalesTerritoryKey = dst.SalesTerritoryKey
GROUP BY dst.SalesTerritoryRegion, dst.SalesTerritoryCountry
ORDER BY TotalSales DESC;
"""
},
{
"question": "查询年收入前10的客户及其购买总额",
"sql": """
SELECT TOP 10
dc.FirstName,
dc.LastName,
dc.YearlyIncome,
SUM(fis.SalesAmount) AS TotalPurchases
FROM DimCustomer dc
INNER JOIN FactInternetSales fis ON dc.CustomerKey = fis.CustomerKey
GROUP BY dc.CustomerKey, dc.FirstName, dc.LastName, dc.YearlyIncome
ORDER BY dc.YearlyIncome DESC;
"""
}
]
示例 SQL 的价值:
- 帮助模型"举一反三",将示例应用到类似场景
- 规范 SQL 风格和最佳实践
- 提升复杂查询的生成质量
2.3 自定义提示词规则
定义全局适用的业务规则:
全局规则:
1. 所有金额字段统一使用 money 类型,注意精度问题
2. 时间范围查询优先使用 DateKey 关联 DimDate 表
3. 产品相关查询需要考虑产品层次结构: Product → Subcategory → Category
4. 客户相关查询注意 GeographyKey 关联地理位置信息
5. 销售数据有两个来源: FactInternetSales (互联网销售) 和 FactResellerSales (经销商销售)
6. 日期字段有三种: OrderDate (下单日期), DueDate (到期日期), ShipDate (发货日期)
7. 金额计算注意货币类型: CurrencyKey 关联 DimCurrency
8. 促销活动: PromotionKey 关联 DimPromotion
三、业务上下文配置:让模型"懂业务"
3.1 术语配置:业务语言 → 技术语言
业务人员说的"大客户",数据库里可能是 YearlyIncome > 100000。术语配置是关键桥梁:
json
{
"terminology": [
{
"business_term": "大客户",
"technical_mapping": "YearlyIncome > 100000",
"description": "年收入超过10万美元的客户"
},
{
"business_term": "互联网销售",
"technical_mapping": "FROM FactInternetSales",
"description": "通过网站直接销售给客户的订单"
},
{
"business_term": "经销商销售",
"technical_mapping": "FROM FactResellerSales",
"description": "通过经销商渠道的销售"
},
{
"business_term": "北美地区",
"technical_mapping": "SalesTerritoryGroup = 'North America'",
"description": "北美销售区域(美国、加拿大)"
},
{
"business_term": "欧洲地区",
"technical_mapping": "SalesTerritoryGroup = 'Europe'",
"description": "欧洲销售区域"
},
{
"business_term": "太平洋地区",
"technical_mapping": "SalesTerritoryGroup = 'Pacific'",
"description": "太平洋销售区域(澳大利亚等)"
},
{
"business_term": "自行车类产品",
"technical_mapping": "EnglishProductCategoryName = 'Bikes'",
"description": "自行车类别产品"
},
{
"business_term": "配件类产品",
"technical_mapping": "EnglishProductCategoryName = 'Accessories'",
"description": "配件类别产品"
},
{
"business_term": "服装类产品",
"technical_mapping": "EnglishProductCategoryName = 'Clothing'",
"description": "服装类别产品"
},
{
"business_term": "促销订单",
"technical_mapping": "PromotionKey <> 1",
"description": "参与了促销活动的订单(PromotionKey=1 表示无促销)"
}
]
}
3.2 字段描述增强
清晰的字段描述比字段名更重要:
示例:FactInternetSales 表字段描述
sql
-- ❌ 不好的做法(仅有字段名)
CREATE TABLE FactInternetSales(
ProductKey int,
OrderDateKey int,
CustomerKey int,
SalesAmount money,
...
);
-- ✅ 好的做法(包含详细描述)
-- 销售事实表:记录互联网销售订单明细
CREATE TABLE FactInternetSales(
ProductKey int, -- 产品代理键,关联 DimProduct
OrderDateKey int, -- 订单日期键,关联 DimDate(订单创建日期)
DueDateKey int, -- 到期日期键,关联 DimDate(应付日期)
ShipDateKey int, -- 发货日期键,关联 DimDate(实际发货日期)
CustomerKey int, -- 客户代理键,关联 DimCustomer
PromotionKey int, -- 促销代理键,关联 DimPromotion(1=无促销)
CurrencyKey int, -- 货币代理键,关联 DimCurrency
SalesTerritoryKey int, -- 销售区域代理键,关联 DimSalesTerritory
SalesOrderNumber nvarchar(20), -- 销售订单号(业务主键)
SalesOrderLineNumber tinyint, -- 订单行号(与 SalesOrderNumber 组合为主键)
RevisionNumber tinyint, -- 订单修订版本号
OrderQuantity smallint, -- 订购数量
UnitPrice money, -- 单价
ExtendedAmount money, -- 扩展金额 = UnitPrice × OrderQuantity
UnitPriceDiscountPct float, -- 单价折扣百分比
DiscountAmount float, -- 折扣金额
ProductStandardCost money, -- 产品标准成本
TotalProductCost money, -- 产品总成本
SalesAmount money, -- 销售金额(实际收入)
TaxAmt money, -- 税额
Freight money, -- 运费
CarrierTrackingNumber nvarchar(25), -- 物流跟踪号
CustomerPONumber nvarchar(25), -- 客户采购订单号
OrderDate datetime, -- 订单日期(冗余字段,便于查询)
DueDate datetime, -- 到期日期(冗余字段)
ShipDate datetime, -- 发货日期(冗余字段)
...
);
四、模型微调方法:从通用到专用
4.1 为什么需要微调?
通用大模型虽然能力强大,但在特定领域的 Text2SQL 任务上,仍可能存在:
- 不理解特定业务术语(如"太平洋地区"对应哪个 SalesTerritoryGroup)
- 生成的 SQL 不符合数据仓库规范(如忘记关联 DimDate)
- 复杂查询准确率不足(如多表关联、层次结构查询)
微调可以让模型更好地适应特定场景。
4.2 DB-GPT-Hub 微调实战
DB-GPT-Hub 是一个专注于 Text-to-SQL 微调的开源项目,在 Spider 数据集上达到了 78.9% 的执行准确率(超过 GPT-4 的 76.2%)。
微调流程(以 AdventureWorksDW2016 为例):
1. 数据准备
- 收集 (问题, SQL) 对,例如:
Q: "查询2014年每个产品类别的销售额"
A: "SELECT dc.EnglishProductCategoryName, SUM(fis.SalesAmount)..."
- 数据清洗和增强
* 添加同义问题("查询各产品类别的销售总额")
* 变换时间条件(2014 → 2013, 2015)
* 变换聚合维度(按类别 → 按地区、按客户)
- 划分训练集/验证集(80%/20%)
2. 模型选择
- 推荐: CodeLlama-13B 或 Qwen2.5-Coder-32B
- 量化: 4bit + LoRA 降低显存需求(可在单张 A100 上训练)
3. 训练配置
- 学习率: 2e-4
- Batch Size: 16
- Epochs: 3-5
- 最大序列长度: 2048(AdventureWorks 表名较长)
4. 评估优化
- 使用执行准确率评估(在真实数据库上执行)
- 对比语法准确率 vs 执行准确率
- 分析错误类型:表关联错误、字段错误、聚合错误等
AdventureWorksDW2016 微调数据示例:
json
[
{
"question": "查询销售额最高的前10个产品",
"sql": "SELECT TOP 10 dp.EnglishProductName, SUM(fis.SalesAmount) AS TotalSales FROM FactInternetSales fis INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey GROUP BY dp.EnglishProductName ORDER BY TotalSales DESC"
},
{
"question": "查询每个国家的客户数量",
"sql": "SELECT dg.EnglishCountryRegionName, COUNT(DISTINCT dc.CustomerKey) AS CustomerCount FROM DimCustomer dc INNER JOIN DimGeography dg ON dc.GeographyKey = dg.GeographyKey GROUP BY dg.EnglishCountryRegionName ORDER BY CustomerCount DESC"
},
{
"question": "查询2014年每月的销售趋势",
"sql": "SELECT dd.EnglishMonthName, dd.MonthNumberOfYear, SUM(fis.SalesAmount) AS MonthlySales FROM FactInternetSales fis INNER JOIN DimDate dd ON fis.OrderDateKey = dd.DateKey WHERE dd.CalendarYear = 2014 GROUP BY dd.EnglishMonthName, dd.MonthNumberOfYear ORDER BY dd.MonthNumberOfYear"
}
]
微调的方法门槛高,成本高,周期长,中小短期项目不是很推荐。
五、推理时增强策略:多选最优
5.1 自洽性(Self-Consistency)
生成多个候选 SQL,通过投票选择最优解:
示例:查询每个产品类别的平均销售额
问题: 查询每个产品类别的平均订单金额
候选SQL:
1.
SELECT dc.EnglishProductCategoryName, AVG(fis.SalesAmount) AS AvgAmount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
GROUP BY dc.EnglishProductCategoryName;
2.
SELECT dc.EnglishProductCategoryName, AVG(fis.ExtendedAmount) AS AvgAmount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
GROUP BY dc.EnglishProductCategoryName;
3.
SELECT dc.EnglishProductCategoryName, SUM(fis.SalesAmount)/COUNT(*) AS AvgAmount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
GROUP BY dc.EnglishProductCategoryName;
分析:
- SQL 1 使用 SalesAmount(实际销售金额,包含折扣)
- SQL 2 使用 ExtendedAmount(扩展金额,未含折扣)
- SQL 3 手动计算平均值
投票结果:
SQL 1 和 SQL 3 语义等价 → 选择最常见的逻辑
最终选择: SQL 1(语义最清晰)
硬投票 vs. 软投票:
- 硬投票:选择出现次数最多的 SQL
- 软投票:根据执行结果相似度加权投票(更优)
5.2 MCS-SQL:多提示架构
MCS-SQL 方法在 BIRD 数据集上达到 65.5% 准确率,Spider 上达到 89.6%:
核心流程:
- 多提示生成:使用不同风格的提示词生成多个候选 SQL
- Schema 精炼:从完整 Schema 中筛选相关表和字段
- 置信度评分:评估每个候选 SQL 的可靠性
- 多选机制:综合选择最优解
AdventureWorksDW2016 应用示例:
问题: "查询2014年北美地区自行车类产品的销售额"
提示1(结构化):
表: FactInternetSales, DimProduct, DimProductCategory, DimSalesTerritory, DimDate
条件: CalendarYear=2014, SalesTerritoryGroup='North America', EnglishProductCategoryName='Bikes'
目标: SUM(SalesAmount)
提示2(自然语言):
从销售事实表中查询2014年的销售数据,筛选北美地区和自行车产品,计算总销售额
提示3(SQL模板):
SELECT SUM(fis.SalesAmount)
FROM FactInternetSales fis
JOIN DimDate dd ON fis.OrderDateKey = dd.DateKey
JOIN DimSalesTerritory dst ON fis.SalesTerritoryKey = dst.SalesTerritoryKey
JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
WHERE dd.CalendarYear = ?
AND dst.SalesTerritoryGroup = ?
AND dc.EnglishProductCategoryName = ?
多提示生成的候选SQL经过置信度评分和投票,选择最优解。
六、RAG 增强:注入领域知识
6.1 为什么需要 RAG?
大模型的知识来自训练数据,对于:
- 企业内部业务规则(如"促销订单的 PromotionKey <> 1")
- 实时数据变化(如新增产品类别)
- 非公开的领域知识(如 AdventureWorks 的业务逻辑)
模型无法直接获取。RAG(检索增强生成)通过外挂知识库解决这一问题。
6.2 RAG 在 Text2SQL 中的应用
AdventureWorksDW2016 知识库示例:
用户问题: "查询参与了促销活动的订单数量"
↓
检索相关文档
↓ (找到: "促销订单的 PromotionKey <> 1,PromotionKey=1 表示无促销")
注入到 Prompt
↓
模型生成 SQL:
SELECT COUNT(DISTINCT SalesOrderNumber) AS PromotionOrderCount
FROM FactInternetSales
WHERE PromotionKey <> 1;
典型流程:
-
知识库构建:整理业务规则、术语定义、常见查询模式
知识条目示例: - "互联网销售存储在 FactInternetSales 表" - "经销商销售存储在 FactResellerSales 表" - "产品层次: Product → Subcategory → Category" - "日期维度包含日历年度和财年两种时间维度" - "销售区域分为三大组: North America, Europe, Pacific" -
向量化存储:使用 Embedding 模型向量化
-
相似度检索:根据用户问题检索相关文档
-
Prompt 增强:将检索结果注入提示词
七、完整实践案例:SQLBot 五大配置项
SQLBot是我目前见过国内比较优秀的一个Text2SQL的开源项目。这里以 SQLBot 开源项目为例,简单汇总了下企业级 Text2SQL 系统需要五大配置项:
7.1 配置项详解(AdventureWorksDW2016 实战)
| 配置项 | 作用 | AdventureWorks 示例 |
|---|---|---|
| 表管理 | 字段描述、示例值 | SalesAmount: 实际销售金额(含税、含运费) |
| 表关联关系 | 多表 JOIN 逻辑 | FactInternetSales.CustomerKey → DimCustomer.CustomerKey |
| 术语配置 | 业务→技术映射 | "自行车产品" = EnglishProductCategoryName = 'Bikes' |
| 示例 SQL | 复杂查询模板 | 提供 10-50 个高频查询示例(见下文) |
| 自定义提示词 | 全局规则 | "日期查询优先使用 DateKey 关联 DimDate" |
7.2 AdventureWorksDW2016 示例 SQL 库
sql
-- 示例1: 按产品类别统计销售额
SELECT
dc.EnglishProductCategoryName,
COUNT(DISTINCT fis.SalesOrderNumber) AS OrderCount,
SUM(fis.SalesAmount) AS TotalSales,
AVG(fis.SalesAmount) AS AvgOrderAmount
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
INNER JOIN DimProductCategory dc ON dps.ProductCategoryKey = dc.ProductCategoryKey
GROUP BY dc.EnglishProductCategoryName
ORDER BY TotalSales DESC;
-- 示例2: 按销售区域统计年度销售趋势
SELECT
dst.SalesTerritoryGroup,
dst.SalesTerritoryCountry,
dd.CalendarYear,
SUM(fis.SalesAmount) AS YearlySales
FROM FactInternetSales fis
INNER JOIN DimSalesTerritory dst ON fis.SalesTerritoryKey = dst.SalesTerritoryKey
INNER JOIN DimDate dd ON fis.OrderDateKey = dd.DateKey
GROUP BY dst.SalesTerritoryGroup, dst.SalesTerritoryCountry, dd.CalendarYear
ORDER BY dst.SalesTerritoryGroup, dd.CalendarYear;
-- 示例3: 客户购买行为分析(RFM模型)
SELECT
dc.CustomerKey,
dc.FirstName,
dc.LastName,
MAX(fis.OrderDate) AS LastPurchaseDate,
COUNT(DISTINCT fis.SalesOrderNumber) AS PurchaseFrequency,
SUM(fis.SalesAmount) AS MonetaryValue
FROM DimCustomer dc
INNER JOIN FactInternetSales fis ON dc.CustomerKey = fis.CustomerKey
GROUP BY dc.CustomerKey, dc.FirstName, dc.LastName
ORDER BY MonetaryValue DESC;
-- 示例4: 产品销售排名(按子类别)
SELECT
dps.EnglishProductSubcategoryName,
dp.EnglishProductName,
SUM(fis.OrderQuantity) AS TotalQuantity,
SUM(fis.SalesAmount) AS TotalSales
FROM FactInternetSales fis
INNER JOIN DimProduct dp ON fis.ProductKey = dp.ProductKey
INNER JOIN DimProductSubcategory dps ON dp.ProductSubcategoryKey = dps.ProductSubcategoryKey
GROUP BY dps.EnglishProductSubcategoryName, dp.EnglishProductName
ORDER BY dps.EnglishProductSubcategoryName, TotalSales DESC;
-- 示例5: 促销效果分析
SELECT
dp.EnglishPromotionName,
dp.EnglishPromotionType,
dp.EnglishPromotionCategory,
COUNT(DISTINCT fis.SalesOrderNumber) AS OrderCount,
SUM(fis.DiscountAmount) AS TotalDiscount,
SUM(fis.SalesAmount) AS TotalSales
FROM FactInternetSales fis
INNER JOIN DimPromotion dp ON fis.PromotionKey = dp.PromotionKey
WHERE fis.PromotionKey <> 1 -- 排除无促销的订单
GROUP BY dp.EnglishPromotionName, dp.EnglishPromotionType, dp.EnglishPromotionCategory
ORDER BY TotalSales DESC;
配置优先级:
- 基础能力:表管理 + 表关联关系(必须配置)
- 沟通桥梁:术语配置(高频使用)
- 标准答案:示例 SQL(针对性强)
- 全局规则:自定义提示词(补充约束)
八、实战建议:如何选择优化路径?
8.1 按场景选择
| 场景 | 推荐方法 | 准确率预期 | AdventureWorks 示例 |
|---|---|---|---|
| 简单单表查询 | Few-Shot 示例 | 85%+ | SELECT * FROM DimCustomer WHERE YearlyIncome > 100000 |
| 多表关联查询 | Schema 增强 + 表关联配置 | 80%+ | 产品销售分析(需关联 3-5 张表) |
| 复杂业务查询 | 术语配置 + 示例 SQL + RAG | 75%+ | 客户 RFM 分析、促销效果分析 |
| 特定领域 | 模型微调 | 85%+ | 针对 AdventureWorks 微调 |
| 高准确率要求 | 微调 + 推理时增强 | 90%+ | 企业级报表系统 |
8.2 成本效益分析
| 方法 | 开发成本 | 标注成本 | 硬件需求 | 效果提升 |
|---|---|---|---|---|
| 提示工程 | 低 | 无 | 低 | 10-20% |
| Schema 增强 | 中 | 低 | 低 | 5-10% |
| RAG | 中 | 中 | 中 | 5-15% |
| 模型微调 | 高 | 高 | 高 | 15-25% |
| 推理增强 | 中 | 无 | 中 | 5-10% |
总结:准确率提升路线图
第一阶段(快速见效)
├─ 优化 Schema 呈现(J-Schema)
├─ 添加字段描述和示例值
└─ 配置 10-20 个 Few-Shot 示例(基于 AdventureWorks)
第二阶段(深度优化)
├─ 术语配置(业务语言映射)
│ - "大客户" → YearlyIncome > 100000
│ - "北美地区" → SalesTerritoryGroup = 'North America'
├─ 表关联关系管理
│ - FactInternetSales 的所有外键关系
│ - 产品层次结构(Product → Subcategory → Category)
└─ 自定义提示词规则
- 日期查询使用 DateKey 关联 DimDate
- 产品查询考虑层次结构
第三阶段(持续改进)
├─ 构建 RAG 知识库
│ - AdventureWorks 业务规则
│ - 常见查询模式
├─ 收集反馈数据
│ - 记录用户修正的 SQL
│ - 分析错误模式
└─ 模型微调(可选)
- 使用 AdventureWorks 数据生成训练集
- 针对特定查询类型优化
第四阶段(企业级)
├─ 推理时增强(多候选投票)
├─ 权限控制
│ - 按销售区域限制数据访问
│ - 敏感字段脱敏
└─ 监控与优化
- 查询性能监控
- 准确率持续跟踪
参考资料
- SQLBot 最佳实践指南
- MCS-SQL: 多提示多选架构
- Text2SQL 准确率暴涨 22.6%
- DB-GPT-Hub 微调教程
- Alpha-SQL: 零样本 Text2SQL
- AdventureWorks 示例数据库官方文档