自然语言到SQL语言转义(基于大语言模型的NL2SQL)

为了让不熟悉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:是。

说明

如果原有注释无法修改,您可以使用进阶功能中的自定义表列注释能力进行注释调整,具体请参见自定义表列注释

数据准备

  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));
  2. 将数据表中的信息导入检索索引表。

    将数据表中的信息导入检索索引表的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_infoimage_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。

  • 0:不优化。

  • 1:最后需要执行SQL优化操作,PolarDB for AI将会对生成的SQL语句进一步加工成更优化的SQL。

/*polar4ai*/SELECT * FROM PREDICT (MODEL _polar4ai_nl2sql, select 'id为1的内容是什么?') WITH (basic_index_name='schema_index', to_optimize=1)

basic_index_top

召回表的最相近个数

取值范围:[1,10]。

  • 默认值为3,表示对当前问题只选出最优的3张表。通常设置为1即可。

  • 如果涉及到多张表,可以将该值设置为4或更大,以扩大召回,提高效果。

basic_index_threshold

召回表判断是否相近时所使用的阈值

取值范围:(0,1]。

默认值为0.1,表示当数据库表向量匹配超过0.1时才会被选中。

推荐的问句样例如下:

进行过两次或更多次治疗的专家的ID、角色和名字是什么?
被养最多数量狗的品种名称是什么?
被喂养最多数量的狗都有哪些品种?给我这个品种的名字。
哪一位主人为他或她的狗支付了最多的治疗费?列出主人的ID和姓氏。
告诉我花在他或她的狗身上最多治疗费的主人的ID和姓氏。
总花费最少的治疗类型的描述是什么?

进阶使用

配置问题模板

您可以使用一些通用的问题模板,通过引入特定知识来指导模型,使得模型能按特定的知识来生成SQL语句。

说明

初次体验LLM-based NL2SQL操作时不需要执行该操作。

  1. 创建问题模板表。

    创建问题模板表的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_infotable_name表中的参数,其中param_name表示参数名称,为字符串类型。value是参数的取值样例,为数组类型。如果参数对应的是有限的枚举值,则尽量在value中将所有的值列出来。如果只是样例值,可以列出2~4个值。如果存在相互对应的参数(如intClsintClsCodecategorycategoryCode),则需要通过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字符串[]

  2. 创建问题模板索引表。

    您可以自由选择索引表名,索引表名需符合数据库规范,建议与步骤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));
  3. 将问题模板信息导入索引表。

    将问题模板信息导入索引表的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;
  4. 查看任务状态,并等待任务完成

    您可以根据返回的taskid信息来查看任务执行状态,查看任务执行状态的SQL语句如下:

    /*polar4ai*/SHOW TASK `6d629f42-98c5-11ee-85c2-894330dff27f`;

    等待返回finish信息后,问题模板信息将会被模型引用。您可以执行以下SQL语句来查看问题模板索引信息:

    /*polar4ai*/SELECT * FROM pattern_index;
    说明

    如果polar4ai_nl2sql_pattern表中的数据有更新,则需要重新执行步骤2和步骤3。

  5. 在线使用问题模板。

    您可以执行以下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时均采用。若存在不希望采用又暂时不想删除的配置项,可将is_functional置为0。使该配置行不生效。

  • 1(默认):生效

  • 0:不生效

is_functional=1,则表示该行配置生效。

text_condition

对问题进行文本条件判断。

如果判断为匹配,则使用query_functionformula_function两列进行处理,反之则不使用。

  • 当前支持&&||!!三种条件,分别表示

  • text_condition为空或空字符串时,表示对于所有问题均匹配。

text_condition=‘张三||李四&&!!王五',则表示:如果问题包含张三,或者包含李四且不包含王五,则条件匹配。

例如,对于问题张三今年总销售额多少?,则条件匹配;对于问题李四今年总销售额多少?,依然条件匹配;对于问题李四王五今年总销售额多少?,则不匹配。

query_function

对问题进行处理。

text_condition判断为匹配时使用。

当前支持appenddeletereplace三种处理,整体写作JSON格式字符串。

text_condition={"append":["一","二"],"delete":["?"],"replace":{"张三":"a","李四":"b"}},则表示:当text_condition匹配时,在问题结尾添加,并且删除问题中的,最后将问题中的张三替换为a李四替换为b

例如,对于问题张三今年总销售额是多少?,在text_condition匹配时,会最终被处理为a今年总销售额是多少一二

formula_function

在问题中补充与具体业务/概念相关的计算公式信息或其他信息。

text_condition判断为匹配时使用。

-

formula_function=总销售额:SUM(销售额),则问题中的总销售额,在最终处理时,将使用SUM(销售额)公式作为额外信息一并进行处理。

sql_condition

对大模型生成的SQL进行后置处理的条件。

如果判断为匹配,则使用sql_function对SQL进行处理,反之则不使用。

  • 当前支持对SQL中的表的存在进行判断,支持&&||!!三种条件,分别表示与、或、非。

  • sql_condition为空或空字符串时,表示对于所有SQL均匹配。

sql_condition=user_info||sales_info&&!!stock_info,则表示:如果表user_info在SQL中,或者表sales_info在SQL中且stock_info不在SQL中,则条件匹配。

例如,对于SQLSELECT * FROM user_info,则条件匹配;对于SQLSELECT * FROM sales_info,依然匹配;对于SQLSELECT ss.sale_total FROM sales_info ss join stock_info sk on ss.sku = sk.sku,则不匹配。

sql_function

对SQL进行处理,可用于对业务逻辑中的值映射进行强制处理。

sql_condition判断为匹配时使用。

当前支持replace处理,整体写作JSON格式字符串。

sql_function={"replace":{"status = '已创建'":"status = 0","status = '已完成'":"status = 1","status = '已删除'":"status = 2"}},则表示:当sql_condition匹配时,将SQL中的status = '已创建'替换为status = 0status = '已完成'替换为status = 1status = '已删除'替换为status = 2

示例

id

is_functional

text_condition

query_function

formula_function

sql_condition

sql_function

1

1

张三||李四&&!!王五

{"append":["一","二"],"delete":["?"],"replace":{"张三":"a","李四":"b"}}

2

1

总销售额:SUM(销售额)

3

1

user_info||sales_info&&!!stock_info

{"replace":{"status = '已创建'":"status = 0","status = '已完成'":"status = 1","status = '已删除'":"status = 2"}}

自定义表列注释

在对数据库表中的列添加注释时,如果原有的注释不能改变,您可以在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

专利类型。取值范围如下:

  • 1:发明。

  • 2:实用新型。

  • 3:外观设计。

  • 4:短期专利。

  • 5:其它。

  • 6:译文。

  • 7:检索报告。

  • 8:pct发明。

  • 9:pct实用新型。

2

sy_cd_ms_base_ipr_devdemo_list

知识产权优势和示范企业

certName

知识产权优势和示范企业认定名称。取值范围如下:

  • 1:知识产权优势企业。

  • 2:知识产权示范企业。

宽表支持

当用户判断库中存在列数较多的宽表,或在NL2SQL的使用过程中收到如Please use column index to avoid oversize table information.的错误消息时,可以使用以下流程以支持宽表:

  1. 构建列索引表。

    构建列索引表时,表名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));
  2. 将数据表信息以列为粒度导入列索引表中。

    /*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`;
  3. 在线使用宽表。

    /*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语句中涉及的高级语法越少,查询越准确。