为了让不熟悉SQL语言的用户能方便地从数据库中取数分析,PolarDB for AI推出自研的基于大语言模型的自然语言到数据库查询语言转义(Large Language Model based Nature Language to SQL,简称LLM-based NL2SQL)解决方案,PolarDB for AI会帮助用户将输入的自然语言转换为SQL语句。和传统NL2SQL的方法相比,LLM-based NL2SQL在语言理解方面会更强大,生成的SQL语句中能支持更多的函数,如日期加减等。LLM-based NL2SQL甚至能够理解一些简单的映射关系,如有效->isValid=1
等。经过适当调整后还能理解用户的一些常用SQL搭配,如条件中默认选用datastatus=1
等。本文介绍了LLM-based NL2SQL的使用方法。
前提条件
已开启PolarDB for AI功能。建议增加AI节点时,节点规格选择包含GU30的规格。
已通过PolarDB MySQL版的集群地址连接数据库集群。
使用LLM-based NL2SQL解决方案涉及的表在指定库内。
注意事项
使用LLM-based NL2SQL功能时,提出的问题中要明确表达出限制条件和相关的实体值。同时,尽量将条件前置,然后是需要找的列值对应的实体,最后是可能的列名。示例如下:
超过1个房间的“房子”或“公寓”的属性名称是什么?
其中,超过1个房间是条件,房子和公寓是列值对应的实体,属性名称是可能的列名。
使用说明
规范数据表
NL2SQL的前提是模型要理解表的含义,包括列名代表的意思。所以使用LLM-based NL2SQL时,需要对常用的数据表以及表中的列添加注释。
表注释
表注释能够帮助LLM-based NL2SQL模型更好地理解表的基础信息,能更好地定位SQL语句中涉及的表。注释可以简洁明了地说明表的大致内容。如订单、库存等,注释尽量控制在10个字以内,不要引入过多解释。
列注释
列注释由用户常用的名词或短语构成。例如,订单编号、日期、店铺名称等,能精确体现列名所表达的含义。
您可以在列注释中添加列的样例数据或映射关系。例如,列名为isValid
,它的注释可以为是否有效。0:否。1:是。
。
如果原有注释无法修改,您可以使用进阶功能中的自定义表列注释能力进行注释调整,具体请参见自定义表列注释。
数据准备
构建数据表的检索索引表。
为了提取数据表中的数据,需要构建数据表的检索索引表,支持自由指定表名(需满足数据库规范),本文以
schema_index
为例,构建检索索引表的SQL语句如下:/*创建数据库表索引*/ /*polar4ai*/CREATE TABLE schema_index(id integer, table_name varchar, table_comment text_ik_max_word, table_ddl text_ik_max_word, column_names text_ik_max_word, column_comments text_ik_max_word, sample_values text_ik_max_word, vecs vector_768,ext text_ik_max_word, primary key (id));
将数据表中的信息导入检索索引表。
将数据表中的信息导入检索索引表的SQL语句如下:
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_text2vec, select '') WITH (mode='async', resource='schema') into schema_index;
执行以上SQL语句后,PolarDB for AI默认会对当前库下的所有表执行转向量操作,并对列值进行取样。其中,
into
后需要填写上一步创建的检索索引表名。以上SQL默认会对当前库下的所有表转向量,并会对列进行值取样。with()
内支持多个参数对相关行为进行设置:tables_included:用于设置转向量的表,默认为
''
,表示对所有的表执行转向量操作。设置时多个表名之间使用英文逗号分隔,并拼接为字符串。to_sample:用于设置是否对列值进行取样。对列值进行取样会使得数据表导入索引表的任务时间变长,但在表的列数较少(<15列)时能提高生成的SQL的质量。取值范围如下:
0(默认):表示不对列值取样。
1:表示对列值取样。
columns_excluded:用于设置不参与LLM-based NL2SQL操作的列。默认为
''
,表示所有参与LLM-based NL2SQL操作的表的所有列均参与后续LLM-based NL2SQL操作。设置时需要将选中的表中不参与后续LLM-based NL2SQL操作的列按照table_name1.column_name1,table_name1.column_name2,table_name2.column_name3
的格式拼接为字符串。
示例如下:
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_text2vec, select '') WITH (mode='async', resource='schema', tables_included='graph_info,image_info,text_info', to_sample=1, columns_excluded='graph_info.time,text_info.ext') into schema_index;
该语句表示对当前库中的
graph_info
、image_info
以及text_info
表执行转向量操作,并且对列进行值取样。同时表graph_info
中的time
列和表text_info
中的ext
列不参与后续的LLM-based NL2SQL操作。执行完该语句后,会返回该任务的
taskid
,如bce632ea-97e9-11ee-bdd2-492f4dfe0918
。您可以执行以下命令来查看导入状态,当返回值显示为finish
时,表示索引构建完成。/*polar4ai*/SHOW TASK `bce632ea-97e9-11ee-bdd2-492f4dfe0918`;
说明如果
polar4ai_nl2sql_table_extra_info
中的数据发生变更,则需要重新执行步骤1和步骤2。
在线使用LLM-based NL2SQL
您可以执行以下SQL语句来在线使用LLM-based NL2SQL:
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_nl2sql, select '按照老师名字的字母升序排列,显示老师的名字和安排他们教的课程名称。') WITH (basic_index_name='schema_index');
上述SQL语句中,SELECT后需要输入待转换为SQL语句的用户问题,basic_index_name
为数据库索引表名称。with()
内支持设置的参数如下:
参数名称 | 参数描述 | 取值范围 |
to_optimize | 是否进行SQL优化。 | 取值范围:0或1。默认值为0。
|
basic_index_top | 召回表的最相近个数 | 取值范围:[1,10]。
|
basic_index_threshold | 召回表判断是否相近时所使用的阈值 | 取值范围:(0,1]。 默认值为0.1,表示当数据库表向量匹配超过0.1时才会被选中。 |
推荐的问句样例如下:
进行过两次或更多次治疗的专家的ID、角色和名字是什么?
被养最多数量狗的品种名称是什么?
被喂养最多数量的狗都有哪些品种?给我这个品种的名字。
哪一位主人为他或她的狗支付了最多的治疗费?列出主人的ID和姓氏。
告诉我花在他或她的狗身上最多治疗费的主人的ID和姓氏。
总花费最少的治疗类型的描述是什么?
进阶使用
配置问题模板
您可以使用一些通用的问题模板,通过引入特定知识来指导模型,使得模型能按特定的知识来生成SQL语句。
初次体验LLM-based NL2SQL操作时不需要执行该操作。
创建问题模板表。
创建问题模板表的SQL语句如下:
DROP TABLE IF EXISTS `polar4ai_nl2sql_pattern`; CREATE TABLE `polar4ai_nl2sql_pattern` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `pattern_question` text COMMENT '模板问题', `pattern_description` text COMMENT '模板描述', `pattern_sql` text COMMENT '模板SQL', `pattern_params` text COMMENT '模板参数', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
其中,表名必须以
polar4ai_nl2sql_pattern
开头,表结构中必须包含上述建表语句中的五个列。问题模板是为特定领域知识而制定的,能够方便模型更好地理解当前问题。问题模板由模板问题、模板描述、模板SQL、模板参数四部分组成。
模板问题:带参数的用户问题。会作为输入提供给LLM-based NL2SQL模型。在模板问题中,需要按照
#{XXX}
格式来写入参数。模板描述:对用户问题进行提炼,会将一些实体作为参数。如日期、年份、机构等,这些实体通常会对应表中的特定列。在模板描述中使用
【XXX】
格式表示,并且需要与模板问题中的参数顺序保持一致。模板SQL:与用户问题对应的正确的SQL语句,且需要将一些变量作为参数,参数需要和模板问题中的参数保持一致。
模板问题和模板SQL中同前缀的参数,其参数值存在一一映射关系。例如,
#{category}
和#{categoryCode}
,category
有普通商标、特殊商标和集体商标这三个参数值,则对应的categoryCode
为0、1、2。模板参数:模板参数由一个JSON数组构成,其中每个JSON中的具体参数如下:
table_name:模板SQL中的表名。为字符串类型。
param_info:
table_name
表中的参数,其中param_name
表示参数名称,为字符串类型。value
是参数的取值样例,为数组类型。如果参数对应的是有限的枚举值,则尽量在value
中将所有的值列出来。如果只是样例值,可以列出2~4个值。如果存在相互对应的参数(如intCls
和intClsCode
、category
和categoryCode
),则需要通过list
顺序来映射。例如,intCls
中的化学原料对应inClsCode
中的1,颜料油漆对应inClsCode
中的2。explanation:补充说明。通常是对生成的SQL语句的一些要求,比如,输出哪些信息或者对字段进行说明。
创建问题模板表的示例如下:
主键
模板问题
模板描述
模板SQL
模板参数
1
查询年份为#{issueDate}年项目状态为#{projectStat}状态计划发布的国家标准有哪些?
【年份】【项目状态】计划发布的国家标准有哪些?
SELECT DISTINCT planNum, projectCnName, projectStat FROM sy_cd_me_buss_std_gjbzjh WHERE `planNum` IS NOT NULL AND `dataStatus` != 3 AND `isValid` = 1 AND projectStat=#{projectStat} AND DATE_FORMAT( `issueDate`,'%Y')=#{issueDate}
[{
"table_name":"sy_cd_me_buss_std_gjbzjh","param_info":[{"param_name":"#{issueDate}","value":[2009,2010,2011,2012]},{"param_name":"#{projectStat}","value":["正在征求意见","已发布","正在审查"]}],"explanation":"输出标准名称projectCnName;计划号;项目状态"}]
2
查询商标类型为#{category}国际分类为#{intCls}类型的商标有哪些?
【商标类型】【国际分类】的商标有哪些?
SELECT DISTINCT tmName,regNo,status FROM sy_cd_me_buss_ip_tdmk_new WHERE dataStatus!=3 AND isValid = 1 AND category=#{categoryCode} AND intCls=#{intClsCode}
[{
"table_name":"sy_cd_me_buss_ip_tdmk_new","param_info":[{"param_name":"#{intCls}","value":["化学原料","颜料油漆","日化用品","燃料油脂","医药"]},{"param_name":"#{category}","value":["普通商标","特殊商标","集体商标"]},{"param_name":"#{intClsCode}","value":[1,2,3,4,5]},
{"param_name":"#{categoryCode}","value":[0,1,2]}],"explanation":"输出商标名称tmName、regNo申请号/注册号、status商标状态。注:category为常量映射类型。变量映射字段:categoryCode。
intCls为常量映射类型。变量映射字段:intClsCode。"}]
说明当不需要填写模板参数时,您可以将模板参数值设置为以下任意一种形式:
NULL
空字符串
空的list字符串[]
创建问题模板索引表。
您可以自由选择索引表名,索引表名需符合数据库规范,建议与步骤1中指定的表名保持一致。接下来的介绍将以
pattern_index
作为示例:创建问题模板索引表的SQL语句如下:
/*创建问题模版索引*/ /*polar4ai*/drop table if exists pattern_index; /*polar4ai*/create table pattern_index(id integer, pattern_question text_ik_max_word, pattern_description text_ik_max_word, pattern_sql text_ik_max_word, pattern_params text_ik_max_word, pattern_tables text_ik_max_word, vecs vector_768, primary key (id));
将问题模板信息导入索引表。
将问题模板信息导入索引表的SQL语句如下:
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_text2vec, select '') WITH (mode='async', resource='pattern') into pattern_index;
其中,
INTO
后需要填写步骤2中的问题模板索引表名。with()
中支持多个参数,mode='async'
表示异步,resource='pattern'
表示对问题模板进行向量化,这2个参数的值不可缺少,亦不可更改。除此之外,with()
中还支持配置pattern_table_name
参数,不填写该参数值时默认为polar4ai_nl2sql_pattern
。填写时必须填写以polar4ai_nl2sql_pattern
开头的字符串格式的表名,该名称对应步骤1创建问题模板索引表时指定的表名。如果用户希望为不同场景或业务维护不同的pattern index
,可以在创建问题模板索引表时指定不同的表名。如为用户相关场景创建polar4ai_nl2sql_pattern_user
表,则第二步创建问题模板索引时,将索引名定为pattern_index_user
,在将信息导入索引表时,使用如下SQL语句:/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_text2vec, select '') WITH (mode='async', resource='pattern', pattern_table_name='polar4ai_nl2sql_pattern_user') into pattern_index_user;
查看任务状态,并等待任务完成。
您可以根据返回的
taskid
信息来查看任务执行状态,查看任务执行状态的SQL语句如下:/*polar4ai*/SHOW TASK `6d629f42-98c5-11ee-85c2-894330dff27f`;
等待返回
finish
信息后,问题模板信息将会被模型引用。您可以执行以下SQL语句来查看问题模板索引信息:/*polar4ai*/SELECT * FROM pattern_index;
说明如果
polar4ai_nl2sql_pattern
表中的数据有更新,则需要重新执行步骤2和步骤3。在线使用问题模板。
您可以执行以下SQL语句来在线使用LLM-based NL2SQL和问题模板:
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_nl2sql, select '按照老师名字的字母升序排列,显示老师的名字和安排他们教的课程名称。') WITH (basic_index_name='schema_index',pattern_index_name='pattern_index');
上述SQL语句中,SELECT后需要输入待转换为SQL语句的用户问题,
basic_index_name
为数据库索引表名称,pattern_index_name
为问题模板索引名称。with()
内支持设置的与问题模板有关的参数如下(其他参数说明请参见在线使用LLM-based NL2SQL):参数名称
参数描述
取值范围
pattern_index_top
召回问题模板的最相近个数。
取值范围:[1,10]。
默认值为2,表示对当前问题模板只选出最优的2条模板。
pattern_index_threshold
召回问题模板是否相近时所使用的阈值。
取值范围:(0,1]。
默认值为0.85,表示当问题模板向量匹配超过0.85时才会被选中。
构建配置表
若您希望对问题进行前置处理,或者对最终生成的SQL进行后置处理时,可以使用配置表进行配置。
适用场景
场景一:对问题中的确定性词语进行替换,如人名、行业用语、商品名替换等。
比如:对所有包含
张三
的问题,将张三
替换为ZS001
。此时,对于问题张三上个月的销售额是多少?
、张三今年的总销售额是多少?
,在最终调用大模型之前都可以通过配置表将其全部前置处理为ZS001上个月的销售额是多少?
、ZS001今年的总销售额是多少?
。场景二:对包含特定词语的问题补充额外信息。
比如:对所有包含
总销售额
的问题,都补充总销售额
的计算公式总销售额:SUM(销售额)
,在最终调用大模型之前都可以通过配置表添加此类信息,当问题满足对应的条件时进行补充。场景三:对特定表或列的值进行映射替换。
比如:对所有最终关于表user_info的SQL,都将其中的
status = '已创建'
替换为status = 0
,作为一种列值映射的兜底措施。
语法说明
构建配置表的SQL语句如下:
CREATE TABLE `polar4ai_nl2sql_llm_config` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`is_functional` int(11) NOT NULL DEFAULT '1' COMMENT '是否生效',
`text_condition` text COMMENT '文本条件',
`query_function` text COMMENT '查询处理',
`formula_function` text COMMENT '公式信息',
`sql_condition` text COMMENT 'SQL条件',
`sql_function` text COMMENT 'SQL处理',
PRIMARY KEY (`id`)
);
相关说明如下:
列名 | 说明 | 取值范围 | 示例 |
is_functional | 表示该行配置是否生效。 说明 当配置表存在时,默认每次进行NL2SQL时均采用。若存在不希望采用又暂时不想删除的配置项,可将 |
| 若 |
text_condition | 对问题进行文本条件判断。 如果判断为匹配,则使用 |
| 若 例如,对于问题 |
query_function | 对问题进行处理。 当 | 当前支持 | 若 例如,对于问题 |
formula_function | 在问题中补充与具体业务/概念相关的计算公式信息或其他信息。 当 | - | 若 |
sql_condition | 对大模型生成的SQL进行后置处理的条件。 如果判断为匹配,则使用 |
| 若 例如,对于SQL |
sql_function | 对SQL进行处理,可用于对业务逻辑中的值映射进行强制处理。 当 | 当前支持 | 若 |
示例
id | is_functional | text_condition | query_function | formula_function | sql_condition | sql_function |
1 | 1 |
|
| |||
2 | 1 |
| ||||
3 | 1 |
|
|
自定义表列注释
在对数据库表中的列添加注释时,如果原有的注释不能改变,您可以在polar4ai_nl2sql_table_extra_info
表中对列添加注释,且在使用LLM-based NL2SQL解决方案的过程中,该表中的注释会覆盖原表中的注释内容。
语法说明
polar4ai_nl2sql_table_extra_info
表的建表语句如下:
DROP TABLE IF EXISTS `polar4ai_nl2sql_table_extra_info`;
CREATE TABLE `polar4ai_nl2sql_table_extra_info` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`table_name` text COMMENT '表名称',
`table_comment` text COMMENT '表说明',
`column_name` text COMMENT '列名称',
`column_comment` text COMMENT '列说明',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
示例
id | table_name | table_comment | column_name | column_comment |
1 | sy_cd_me_buss_ip_patn_new | 专利 | patType | 专利类型。取值范围如下:
|
2 | sy_cd_ms_base_ipr_devdemo_list | 知识产权优势和示范企业 | certName | 知识产权优势和示范企业认定名称。取值范围如下:
|
宽表支持
当用户判断库中存在列数较多的宽表,或在NL2SQL的使用过程中收到如Please use column index to avoid oversize table information.
的错误消息时,可以使用以下流程以支持宽表:
构建列索引表。
构建列索引表时,表名
column_index
可以自由指定,但不能与库中已存在的表名相同,一个库中存在一张列索引表即可。建表语句如下:/*polar4ai*/CREATE TABLE column_index(id integer, table_name varchar, table_comment text_ik_max_word, column_name text_ik_max_word, column_comment text_ik_max_word, is_primary integer, is_foreign integer, vecs vector_768, ext text_ik_max_word, primary key (id));
将数据表信息以列为粒度导入列索引表中。
/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_text2vec, select '') WITH (mode='async', resource='column') into column_index;
执行该语句后,会显示一个
taskid
,如bce632ea-97e9-11ee-bdd2-492f4dfe0918
。通过执行以下SQL语句可以查看导入状态,当显示finish后,则表示索引构建完成。/*polar4ai*/SHOW TASK `bce632ea-97e9-11ee-bdd2-492f4dfe0918`;
在线使用宽表。
/*polar4ai*/SELECT * FROM PREDICT (MODEL_polar4ai_nl2sql, SELECT '按照老师名字的字母升序排列,显示老师的名字和安排他们教的课程名称。') WITH (basic_index_name='schema_index',column_index_name='column_index');
其中,无论使用哪一个
schema index
或者pattern index
,在数据库中都可以使用同一个column_index_name
,不需要像schema
或者pattern
一样区分场景或者业务语义,因为这里仅作信息的精简。column_index_name
参数对于绝大多数没有超长的请求不会产生影响,仅仅对于触发了长度限制的NL2SQL,会通过column_index_name
精简表信息,因此会丢失一部分准确度,但避免了因Prompt过长导致大模型报错的问题。
小结
LLM-based NL2SQL属于AI算法,AI算法的效果与诸多因素有关。为了使得查询结果不脱离预期,总结了下列可能会影响整体准确率的几个因素:
表和列注释的丰富程度:每张表及表中的列都添加注释,会提高查询的准确率。
用户问题与表中列注释的匹配程度:用户问题中的关键词和列注释保持一致,语义上越接近,查询效果越好。
生成的SQL语句长度:SQL语句中涉及的列越少、条件越简单,查询会越准确。
SQL语句中的逻辑复杂程度:SQL语句中涉及的高级语法越少,查询越准确。