润乾 NLQ 实践(TPCH 主题)

1 数据准备

TPCH 数据集,8 个数据表及其表间关系。

..

数据规模:

记录数

REGION

5

NATION

25

PART

10

SUPPLIER

10000

PARTSUPP

159750

CUSTOMER

11572

ORDERS

12380

LINEITEM

59206

数据集下载:

tpch_dataset.rar

2 元数据准备

2.1 DQL 元数据

DQL 元数据用来描述数据库表间关系,从而将多表查询转换成单表查询。

连接数据源,新建元数据,导入 TPCH 数据集的 8 个数据库表

..

设置主键

勾选各个表的主键字段

..

设置外键

根据表间关联关系,设置各个表间外键。

外键名

外键所在表

外键字段

引用目标表

引用字段

fk_nation_region

NATION

N_REGIONKEY

REGION

R_REGIONKEY

fk_supplier_nation

SUPPLIER

S_NATIONKEY

NATION

N_NATIONKEY

fk_customer_nation

CUSTOMER

C_NATIONKEY

NATION

N_NATIONKEY

fk_partsupp_part

PARTSUPP

PS_PARTKEY

PART

P_PARTKEY

fk_partsupp_supplier

PARTSUPP

PS_SUPPKEY

SUPPLIER

S_SUPPKEY

fk_orders_customer

ORDERS

O_CUSTKEY

CUSTOMER

C_CUSTKEY

fk_lineitem_orders

LINEITEM

L_ORDERKEY

ORDERS

O_ORDERKEY

fk_lineitem_part

LINEITEM

L_PARTKEY

PART

P_PARTKEY

fk_lineitem_supplier

LINEITEM

L_SUPPKEY

SUPPLIER

S_SUPPKEY

添加假表

假表用于生成对应的维度以方便汇总。

..

再分别设置各个表与假表的外键关系,比如 PART 表与假表的外键关系:

..

修改维名

将维名修改成易识别的名称

..

这里要注意的是日期维度,在日期维上设置了层函数用于计算不同层次的日期维,比如:年维 = year(日期);年月维 = year(日期)*100+month(日期)

DQL 元数据下载:

tpch.lmd.rar

测试元数据

执行 DQL 测试元数据。

外键关联查询:

SELECT o.o_orderkey,o.o_orderdate,o.o_custkey.c_name,o.o_custkey.c_nationkey.n_name

FROM orders o

WHERE o_orderstatus='O'

..

过滤后按维(层)分组汇总:

SELECT count(1) cnt,sum(o_totalprice) amt

FROM orders

WHERE o_totalprice>3000

BY o_orderdate# 年月

..

多表按维对齐汇总:

SELECT c.count(1) 客户数,s.count(1) 供应商数

ON 国家

FROM customer c

BY c.c_nationkey

union supplier s

BY s.s_nationkey

..

2.2 DQL JDBC

DQL 提供标准 JDBC,配置后方便后续词典使用。

新建目录,设置 service.xml

<?xml version="1.0" encoding="UTF-8"?>

<SERVICE logicmetadata="tpch0323.lmd">

<DB type="HSQL">

<CONNECTION name="db2" type="jdbc" url="jdbc:hsqldb:hsql://127.0.0.1/tpch"

driver="org.hsqldb.jdbcDriver" user="sa" password=""/>

</DB>

<USERS>

<USER name="root" password="root">

<CONNECTION name="db2"/>

</USER>

</USERS>

</SERVICE>

注册 DQL 服务

在 DQL 的 server.xml 中添加:

<SERVICE name="tpch0323"/>

DQL 具备使用详见文档:https://d.raqsoft.com.cn:6443/report/dql/wdzl1.html

3 NLQ 范式

MQL(Metrics Query Language,指标查询语言)是润乾 NLQ 架构中的核心中间层,承担着“承上启下”的关键作用,主要职责包括:

  • 精确语义承载者:将规范文本中可能存在的残余歧义彻底消除,形成精确的查询语义

  • 规范性的保障:通过精心设计的语法结构,将查询能力限制在合理范围内,确保可控性与准确性

  • 复杂查询的支撑:在保持规范性的同时,仍能表达企业级 BI 所需的复杂查询逻辑(如多表关联、嵌套聚合等)

MQL 设计了四类查询范式,基本覆盖了 BI 场景下的典型查询模式:

一、单表明细

基于单一数据源,不涉及聚合计算,仅做数据筛选。例如,文本“订单金额不低于 1000 元的订单”转换后的 MQL 类似于:

SELECT 订单编码, 客户名称, 签单日期, 订单金额

FROM ORDERS

WHERE 订单金额 >= 1000

二、单表聚合

对单一数据源进行分组汇总,支持维度与指标。例如,文本“各国家的订单总金额”转换后的 MQL 类似于:

SELECT sum(订单金额) AS 总金额

ON 国家 AS 国家

FROM ORDERS

BY 国家

三、主子实体

主表关联子表,在主表明细基础上挂载子表聚合结果。例如,规范文本“订单数大于 5 的客户信息以及总订单金额”转换后的 MQL 类似于:

SELECT 客户编码, 客户名称, orders.count(订单编码) AS 订单数, orders.sum(订单金额) AS 总订单金额

FROM CUSTOMER

JOIN ORDERS

HAVING (订单数 > 5)

四、多维对齐汇总

按同一维度对齐多个数据源的指标。例如,规范文本“各个国家的客户数和供应商数”转换后的 MQL 类似于:

SELECT customer.count(1) AS 客户数, supplier.count(1) AS 供应商数

ON 国家

FROM CUSTOMER BY 所在国家

JOIN SUPPLIER BY 所在国家

说明:MQL 的具体语法和详细规则,需参考产品文档和相关技术资料,本章仅作概念性介绍。

4 NLQ 词典

词典是 NLQ 的核心,本章内容需要重点掌握。

4.1 新建词典,导入元数据

连接数据源

..

URL:

jdbc:datalogic://?lmd=../services/tpch0323/conf/tpch0323.lmd&db.url=jdbc:hsqldb:hsql://127.0.0.1/tpch&db.driver=org.hsqldb.jdbcDriver&db.user=sa&db.password=&db.type=13

新建词典后默认会加载比较词、聚合词、连词、存在词、无效词、非法词等通用词型,可以大幅简化这部分工作。有不合适的词可以自行修改。

..

接下来,选择工具 - 导入元数据,导入 DQL 元数据。

..

导入后,原 DQL 元数据中的假表仅用于维度生成,在“表”区域并不需要,可以删除,只保留数据集的实体表即可。

4.2 设置字段词与实体

前面的步骤完成后还不能查询,我们首先希望做一些基础查询,像明细查询、数据过滤、分组汇总等。比如下面这些查询目标

1. 查询明细

签单日期 金额 订单状态

2. 明细过滤

账户余额 大于 5000 客户名称 客户电话 所属行业

所属行业是 'BUILDING' 的客户名称 客户地址 所在国家

所属国家等于 18 客户名称 客户地址 客户电话

3. 汇总查询

下单客户 订单数 订单总金额

4. 汇总后过滤

订单数 大于 2 下单客户

导入元数据后,修改表中宏字段的标识为业务可识别的名称,标识最终会用于数据查询后显示的列名。

再定义字段词,字段词是用户查询时输入的词,所以可能有多种表述,比如订单日期可能说签单日期,也可能是下单日期。

各个表的字段词如下:

【REGION】

  • 区域编号:REGION.R_REGIONKEY,字段词:区域编号, 区域编码, 区域 ID

  • 区域名称:REGION.R_NAME,字段词:名称, 地区

【NATION】

  • 国家编号:NATION.N_NATIONKEY,字段词:国家编码, 国家编号, 国家 ID

  • 国家名称:NATION.N_NAME,字段词:名称

  • 所在区域:NATION.N_REGIONKEY,字段词:所在区域, 所属区域, 地区

【PART】

  • 零件编号:PART.P_PARTKEY,字段词:零件编号, 零件编码, 产品编码, 零件 ID

  • 零件名称:PART.P_NAME,字段词:名称, 零件, 产品名称

  • 制造商名称:PART.P_MFGR,字段词:制造商名称, 制造商, 厂商, 生产商

  • 品牌:PART.P_BRAND,字段词:品牌

  • 零件类型:PART.P_TYPE,字段词:类型, 类别

  • 尺寸规格:PART.P_SIZE,字段词:规格, 尺寸

  • 包装方式:PART.P_CONTAINER,字段词:包装方式, 包装

  • 零售价:PART.P_RETAILPRICE,字段词:零售价, 单价

【SUPPLIER】

  • 供应商编号:SUPPLIER.S_SUPPKEY,字段词:供应商编码, 供应商编号, 供应商 ID

  • 供应商名称:SUPPLIER.S_NAME,字段词:名称, 公司名

  • 供应商地址:SUPPLIER.S_ADDRESS,字段词:地址

  • 所在国家:SUPPLIER.S_NATIONKEY,字段词:所在国家, 所属国家

  • 联系电话:SUPPLIER.S_PHONE,字段词:联系电话, 电话

  • 账户余额:SUPPLIER.S_ACCTBAL,字段词:账户余额, 余额

【PARTSUPP】

  • 零件编号:PARTSUPP.PS_PARTKEY,字段词:零件编号

  • 供应商编号:PARTSUPP.PS_SUPPKEY,字段词:供应商编号

  • 可用供应量:PARTSUPP.PS_AVAILQTY,字段词:可用供应量, 可用数量, 供应量, 库存量, 库存

  • 供应成本:PARTSUPP.PS_SUPPLYCOST,字段词:供应成本, 批发价, 成本价

【CUSTOMER】

  • 客户编号:CUSTOMER.C_CUSTKEY,字段词:客户编码, 客户编号, 客户号, 客户 ID

  • 客户名称:CUSTOMER.C_NAME,字段词:名称, 公司名称

  • 客户地址:CUSTOMER.C_ADDRESS,字段词:地址

  • 所在国家:CUSTOMER.C_NATIONKEY,字段词:所在国家, 所属国家

  • 客户电话:CUSTOMER.C_PHONE,字段词:联系电话, 电话

  • 账户余额:CUSTOMER.C_ACCTBAL,字段词:账户余额, 余额, 账户金额, 客户账户余额

  • 市场细分:CUSTOMER.C_MKTSEGMENT,字段词:市场细分, 类型, 行业, 所属行业

【ORDERS】

  • 订单编码:ORDERS.O_ORDERKEY,字段词:订单编码, 订单号, 订单 ID

  • 客户编码:ORDERS.O_CUSTKEY,字段词:客户

  • 订单状态:ORDERS.O_ORDERSTATUS,字段词:订单状态, 状态

  • 订单总金额:ORDERS.O_TOTALPRICE,字段词:订单总金额, 订单金额, 订单总额, 总价

  • 订单日期:ORDERS.O_ORDERDATE,字段词:签单日期, 下单日期, 订单日期

  • 订单优先级:ORDERS.O_ORDERPRIORITY,字段词:订单优先级, 优先级

  • 业务员:ORDERS.O_CLERK,字段词:业务员, 职员, 处理职员, 经办人

  • 发货优先级:ORDERS.O_SHIPPRIORITY,字段词:发货优先级

【LINEITEM】

  • 订单编码:LINEITEM.L_ORDERKEY,字段词:订单编码

  • 零件编码:LINEITEM.L_PARTKEY,字段词:零件编码, 产品编码

  • 供应商编码:LINEITEM.L_SUPPKEY,字段词:供应商编码

  • 发货日期:LINEITEM.L_SHIPDATE,字段词:发货日期

  • 承诺到货日期:LINEITEM.L_COMMITDATE,字段词:承诺到货日期, 承诺日期

  • 运输方式:LINEITEM.L_SHIPMODE,字段词:运输方式, 配送方式

..

把所有表都设置完字段词就可以进行汉语查询了。

查询实验

在词典编辑器中,工具 - 查询实验。

点击“搜索”,可以看到搜索结果:

..

点击“浏览数据”可以看到查询结果集。

..

在“搜索结果”选项卡下,点击“查看明细”,可以看到具体的详细信息(JSON 格式)。

..

把这个结果结构化一下来看:

{
  "words": [
    {
      "name": "订单",
      "position": 0,
      "type": "实体词",
      "field": ""
    },
    {
      "name": "数",
      "position": 2,
      "type": "聚合词",
      "field": ""
    },
    {
      "name": " ",
      "position": 3,
      "type": "无效词",
      "field": ""
    },
    {
      "name": "大于",
      "position": 4,
      "type": "比较词",
      "field": ""
    },
    {
      "name": "2",
      "position": 6,
      "type": "数值",
      "field": ""
    },
    {
      "name": " ",
      "position": 7,
      "type": "无效词",
      "field": ""
    },
    {
      "name": "下单客户",
      "position": 8,
      "type": "字段词",
      "field": ""
    }
  ],
  "displayStatement": "订单 数 大于 2,下单客户",
  "mql": "SELECT ORDERS.count(1) AS \"订单数\" ON CUSTOMER AS \"下单客户\" FROM ORDERS BY 客户 HAVING (ORDERS.count(1)>2)",
  "on": [
    {
      "name": "下单客户",
      "dim": "CUSTOMER"
    }
  ],
  "from": [
    {
      "table": "订单",
      "field": [
        {
          "name": "订单数",
          "column": "订单数",
          "dim": null,
          "select": true,
          "sum": "count",
          "where": "大于 2",
          "or": false
        }
      ],
      "by": [
        "客户"
      ]
    }
  ],
  "unknown": null,
  "error": ""
}

下面解析一下这个 JSON 串的含义:

words列出了汉语查询中各个词的分析结果,包括词名称、位置、类型等,比如。

displayStatement中列出了查询语句中与显示字段相关的各个词。

mql为分析后,需要在汉语查询中执行的 mql 语句:SELECT ORDERS.count(1) AS 订单数 ON CUSTOMER AS 下单客户 FROM ORDERS BY 客户 HAVING (ORDERS.count(1)>2,mql 语句将在执行时将转换为 dql 语句并发送到 DQL 服务器执行查询。

on为表间关联查询时的对齐关系中的字段名和维名。

from列出了查询中涉及的各个子句表。其中 table 为表名,field 为表中各个字段的信息,by 为表需要分组统计时的维或者字段。字段信息中,name 为字段名,dim 为字段关联的维,select 为字段是否选出,sum 为计算时字段的聚合处理方式,where 为字段相关的过滤条件。

unknown为无法识别的词,可能是无效词或者字符串型常数等。

error为没有结果时的错误信息。

上面能完成明细以及汇总查询,还可以汇总后的数据过滤,使用时需要写清具体的字段词,但实际业务中关心的某个数据项可能包含多列信息,这时一个个写出来就比较麻烦了。使用实体可以解决这个问题。

实体会包含多列,或者说多个字段描述了一个实体。

..

定义实体需要设置实体名称以及实体词(用于查询)以及包含(需要显示)的字段。

有了实体以后再查询:

账户余额超过 4000 的客户

..

就会把客户的所有相关信息(实体中设置的显示列)都列出,而不必写上多个字段词了。

一个表可以包含多个实体,比如客户表有客户实体,还有高价值客户(余额大于 5000 的客户)。通过设置实体过滤条件来定义更多实体。

..

类似的,还可以定义这些实体:

【PART】零件,高价值零件(零售价 >500)

【ORDERS】订单,新订单(year(签单日期)=year(now())),已完成订单(订单状态 ='F'),未完成订单(订单状态 ='O')

【LINEITEM】订单明细,未发货明细(发货状态 ='O'),已退货明细(退货标记 ='R')

再使用新增的实体进行查询实验:

未完成订单

..

可以看到查询时会自动加上实体中添加的过滤条件。

4.3 使用量纲

对于“账户余额超过 4000 的客户”的查询,实际查询时经常会会指定单位,比如:

账户余额超过 400万元的客户

在实体的量纲单位处指定单位即可。

..

这时再查询:

..

就可以正常得到查询结果了。

量纲可以自行增加和修改,在词典新建时自动添加了一些量纲和单位,以及各个单位之间的换算。

..

4.4 设置常数词

前面的例子“所属国家等于 18 客户名称 客户地址 客户电话”,查询语句中使用了编码进行过滤,而实际业务会直接使用名称。比如:

所属国家是中国的客户

甚至直接会希望直接写:

中国客户

为了达到这样的目标,可以为词典设置常数词。常数词可以从元数据导入,用表字段生成常数词。

..

常数词名称可以手动添加,也可以通过其他工具导入。

..

还可以在生成词典时进行“一键加载”常数词(数据库中维字段值)

..

所有的维表都能一次性加载,可以大幅简化常数词创建过程。

维度及其常数词列表:

【区域, 地区】

常数词名称, 真实值

非洲,0
美洲,1
亚洲,2
欧洲,3
中东,4

【国家】

常数词名称, 真实值

阿尔及利亚,0
阿根廷,1
巴西,2
加拿大,3
埃及,4
埃塞俄比亚,5
法国,6
德国,7
印度,8
印度尼西亚,9
伊朗,10
伊拉克,11
日本,12
约旦,13
肯尼亚,14
摩洛哥,15
莫桑比克,16
秘鲁,17
中国,18
罗马尼亚,19
沙特阿拉伯,20
越南,21
俄罗斯,22
英国,23
美国,24

【行业】

常数词名称, 真实值

汽车,AUTOMOBILE
建筑,BUILDING
家具,FURNITURE
家居,HOUSEHOLD
机械,MACHINERY

【订单状态, 状态】

常数词名称, 真实值

已完成,F

未完成,O

待处理,P

【订单优先级, 优先级】

常数词名称, 真实值

紧急, 1-URGENT

高, 2-HIGH

中, 3-MEDIUM

未指定, 4-NOT SPECIFIED

低, 5-LOW

【退货标记, 退货】

常数词名称, 真实值

全部退回,A

未退回,N

部分退回,R

【发货状态】

常数词名称, 真实值

已发货,F

未发货,O

【运输方式】

常数词名称, 真实值

航空,AIR
离岸价,FOB
邮件,MAIL
铁路,RAIL
航空挂号,REG AIR
海运,SHIP
卡车,TRUCK

【品牌】

常数词名称, 真实值

Brand#11,Brand#11

Brand#13,Brand#13

Brand#44,Brand#44

Brand#54,Brand#54

【零件类型】

常数词名称, 真实值

大型拉丝黄铜,LARGE BRUSHED BRASS
大型抛光钢,LARGE BURNISHED STEEL
促销抛光铜,PROMO BURNISHED COPPER
小型电镀黄铜,SMALL PLATED BRASS
小型电镀铜,SMALL PLATED COPPER
标准抛光黄铜,STANDARD POLISHED BRASS
标准抛光锡,STANDARD POLISHED TIN

【包装方式】

常数词名称, 真实值

巨型包装,JUMBO PKG
大罐,LG CAN
大箱,LG CASE
大桶,LG DRUM
中袋,MED BAG
中桶,MED DRUM
小袋,SM BAG
小包装,SM PKG
缠绕箱,WRAP CASE

在维词下面的常数词部分增加了编码与名称以后,再查询时就可以使用名称进行过滤了。查询:

中国的客户

..

不需要指定参数对应的字段“所属国家”,“中国”作为常数词会自动关联到“国家”维度进行过滤,从而实现更自然更口语化的查询方式。

4.5 完善维词

维度作为汇总查询的基准,设置适合的维度及其维词可以更方便观察数据,有时可能基于同一个维度汇总不同数据。比如要查询:

不同国家的客户数和供应商数

按年汇总 订单金额 订购数量

签单日期是去年的订单

修改维词,同一个维也可以设置多个维词以方便查询。

..

注意,在词典中设置的维词(维名称)要与 DQL 元数据一致。

来做查询实验,查询:

不同国家的客户数和供应商数

..

可以看到执行的 MQL:

SELECT table_alias_name_1.count(1) AS \"客户数 \",table_alias_name_2.count(1) AS \"供应商数 \" ON NATION AS 国家 FROM CUSTOMER AS table_alias_name_1 BY 所在国家 FULL JOIN SUPPLIER AS table_alias_name_2 BY 所在国家

跨两个表按照“国家”维度进行对齐汇总,有了维度以后就可以轻松完成这类基于相同维度的汇总计算了。

有些维度像日期维度会经常用到,而日期类维度有多种层地,比如年、月、季度、周等。在 DQL 元数据中已经特别增加了这些维度,日期的不同层次都可以通过日期计算得到。在词典中也同样配置不同的日期维词。

..

对应的维词设置常数词,比如年下有今年、去年这些表达。由于真实值是表达式,所以要勾选“值是表达式类型”。

日期维及其常数词:

【年】

常数词名称, 真实值

今年,year(now())

去年,year(ADDYEARS(now(),-1))

明年,year(ADDYEARS(now(),1))

前年,year(ADDYEARS(now(),-2))

本年,year(now())

前一年,year(ADDYEARS(now(),-1))

上一年,year(ADDYEARS(now(),-1))

【年月, 月】

常数词名称, 真实值

上个月,year(ADDMONTHS(now(),-1))*100+month(ADDMONTHS(now(),-1))

上月,year(ADDMONTHS(now(),-1))*100+month(ADDMONTHS(now(),-1))

本月,year(now())*100+month(now())

下月,year(ADDMONTHS(now(),1))*100+month(ADDMONTHS(now(),1))

下个月,year(ADDMONTHS(now(),1))*100+month(ADDMONTHS(now(),1))

【日, 天】

常数词名称, 真实值

今天,date(now())

昨天,ADDDAYS(now(),-1)

前天,ADDDAYS(now(),-2)

明天,ADDDAYS(now(),1)

后天,ADDDAYS(now(),2)

再做查询实验:

按年汇总 订单金额 订购数量

..

生成的 MQL:

SELECT table_alias_name_1.sum(总价) AS \"汇总订单金额 \",table_alias_name_1.sum(订购数量) AS \"订购数量 \" ON 年 AS 年 FROM LINEITEM AS table_alias_name_1 BY 发货日期 #年

可以看到,自动按“发货日期”的“年”层次进行分组汇总了。

再查询:

签单日期是去年的订单

..

可以看到生成的 MQL 语句按照年份的常数词“去年”过滤了数据。

还可以做聚合上的条件查询,比如:

(去年订单数) (今年订单数)

按客户统计 ( 去年订单数)(今年订单数)

来做查询实验:

..

生成的 MQL:

SELECT 名称, 所在国家, 电话, 账户余额, 市场细分,table_alias_name_1.count(1) AS \"去年订单数 \",table_alias_name_2.count(1) AS \"今年订单数 \" ON CUSTOMER AS 客户编号 FROM CUSTOMER BY 客户编号 LEFT JOIN ORDERS AS table_alias_name_1 WHERE (订单日期 #年 =year(ADDYEARS(now(),-1)))BY 客户 LEFT JOIN ORDERS AS table_alias_name_2 WHERE ( 订单日期 #年 =year(now())) BY 客户

4.6 设置宏词

有时我们会用更加口语化的表达来查询,比如:

已售罄的商品

签单日期在秋天的订单

前半年的销售额

这里“已售罄”业务上代表库存为 0,秋天则是指 9 月到 11 月,前半年介于 1 月到 6 月之间。

通过设置词典中的“宏词”可以实现这种更口语化的表达。

..

输入对应的宏词就会被替换成后面的内容。

来做查询实验,查询:

已售罄的零件

..

签单日期在 1992 年上半年的订单编码和订单金额

..

查看 MQL:

SELECT 订单日期 AS \"签单日期 \", 订单编码 AS \"订单编码 \", 订单总金额 AS \"订单金额 \" FROM ORDERS WHERE (订单日期 #年 =year(ADDYEARS(now(),-2))) AND ((订单日期 #月 >=1)AND ( 订单日期 #月 <=6))

有时我们输入的条件不像上面“上半年”“已售罄”这种固定形式而,而是“N 年前”“N 个工作日后”这样的动态条件。比如要查询:

签单日期在 5 年前的订单

需要通过动态宏词来实现,在词典的宏词列表中增加动态宏词(勾选动态宏)。

..

来做查询实验:

签单日期在 5 年前的订单

..

日期自动计算到 5 年前的 2021 年,MQL:

SELECT 订单日期 AS \"签单日期 \", 订单编码, 订单状态, 订单总金额, 业务员 FROM ORDERS WHERE (订单日期 #年 =2021)

4.7 设置字段簇

引入常数词和宏词都能更符合口语化表达,但还不够,尤其条件查询时如果不是针对维过滤,还需要写上字段词才能准确对应条件和字段。像上面的“签单日期在 5 年前的订单”,查询时可能更希望写:

5 年前的订单 / 5 年前签订的订单

通过定义字段簇来实现这个目标。

..

字段簇与实体类似,由一组宏字段组成共同描述一个概念。右边的标记字段类似字段簇的“主键”,作为唯一标识必须选出。

然后在实体中关联对应的字段簇。

..

再做查询实验:

5 年前的订单

..

日期参数自动与字段簇中类型相同的订单日期字段对应完成查询。

类似的,可以定义更多字段簇来表示不同业务概念:

产品、订单、客户、厂家、发货、收货等。

4.8 添加簇词 / 外键簇

前面的查询基本都是基于单表的,“不同国家的客户数和供应商数”这种也只是进行多表对齐,而常见的外键关联并没有体现。比如要查:

订单号 签单日期 订单金额 客户名称 客户电话

直接做查询实验:

..

查询不了,得到了这样的提示。

这是因为我们查询的信息来源于两个表,而这个表的关联信息(外键)并未在查询中指定,所以查询时需要使用“外键名 外键字段”的形式来查询外键表字段。

订单表与客户表的外键字段是客户编码,所以这样查:

订单号 签单日期 订单金额 客户编码 客户名称 客户电话

在引用外键表字段时先指定外键(字段)名称。

..

结果出来了,但是发现结果集列名很怪,用了外键名 + 字段名来命名。所以我们在词典设计时可以把外键字段词改成简短的名称,比如“客户”。

..

同理,客户表的字段词也不必加上“客户”了。

..

再做查询实验:

订单号 签单日期 订单金额 客户 名称 电话

..

这个结果集列名就很正常了。

事实上,我们也可以通过簇词和外键簇词完成相同的目标。字段词仍然使用“客户编码”,然后勾选其为“外键簇”。新建簇词以后,选择外键簇标识为外键簇(标识)“客户”。

..

再查询:

订单号 签单日期 订单金额 客户 名称 电话

..

这里我们还勾选了簇词“具有名词词型”,这样簇词就可以单独使用了。查询:

订单号 签单日期 订单金额 客户 订单状态

..

查询结果中,除了订单表的相关信息外,还列出了客户表的 4 个字段。当簇词当名词用(未指定词内具体字段)时,查询会返回字段簇的标记字段,对于外键簇词(像这里的客户),则自动选择外键表的默认字段簇的标记字段,这里就是客户表的默认字段簇的标记字段(客户编号、名称、账户余额、市场细分)。

..

4.9 设置存在词

有了外键簇以后,我们可以做更复杂针对子表带条件的实体查询,比如:

2019 年总订购数量大于 100 的订单数据

订购数量在订单明细中,需要汇总子表数据并过滤后与主表关联。

来做查询实验:

..

生成的 MQL 语句:

SELECT 订单日期 AS 订单日期, 订单状态, 订单总金额, 业务员,table_alias_name_1.sum(订购数量) AS \"总订购数量 \" ON ORDERS AS 订单编码 FROM ORDERS WHERE (订单日期 #年 =2019) BY 订单编码 LEFT JOIN LINEITEM AS table_alias_name_1 BY 订单 HAVING ((table_alias_name_1.sum( 订购数量)>100))

可以看到已经比较复杂了。

比较随意的口语化查询有时还可能直接问“有没有”,比如:

2024 年有订单的客户

这种判断是否存在的查询可以通过设置存在词来完成。在新建词典时会给出默认的存在词列表。

..

这样我们再查询时:

..

生成的 MQL:

SELECT 名称, 所在国家, 电话, 账户余额, 市场细分 ON CUSTOMER AS 客户编号 FROM CUSTOMER BY 客户编号 LEFT JOIN ORDERS AS table_alias_name_1 WHERE (订单日期 #年 =2024) BY 客户 HAVING ((table_alias_name_1.count(1) is not null))

关联多个表同时将存在词转换成 HAVING 后的条件。

4.10 使用动词

自然语言查询的方式很丰富,有时还可能使用动词的形式。比如:

2025 年销售的订单

去年上半年签单的客户有哪些

供应商 Supplier#000000001 卖给 客户 Customer#000147017 的 订单明细

或者直接查询:

Supplier#000000001 卖给 Customer#000147017 的 订单明细

通过定义动词可以完成上面的查询目标。在词典中增加订单表的动词“签单, 销售, 售出”

..

动词需要关联字段簇,左簇和右簇根据动词情况选择,这里把左右都选上本表的字段簇“订单”。

然后做查询实验:

1992 年销售的订单

..

去年上半年签单的客户有哪些

..

在订单明细表中也增加类似的动词:“卖给, 销售给”

..

动词的左簇和右簇分别是供应商和客户,供应商和客户都是外键簇词。

..

注意这里为了实现动词的查询,订单表的“客户”字段冗余到了订单明细表,并填写广义字段表达式为:LINEITEM.L_ORDERKEY.O_CUSTKEY,即 DQL 中定义的外键关系。

再查询某个产品卖给某个客户的订单明细信息:

供应商 Supplier#000000001 卖给 客户 Customer#000147017 的 订单明细

..

因为设置过维度和常数词,可以使用更简要的写法:

..

4.11 指标

为了更方便数据计算,可以实现定义计算指标,然后在查询时直接使用指标词。指标可以分为一般指标和复杂指标。比如经常要按月汇总订单金额,就可以在指标中定义一个“月总订单金额”。

..

设置固有维度为“年月”,也就是基于年月汇总,指标函数式一个聚合函数:?1.sum(订单总金额),设置指标词:

月总订单金额, 月订单总额, 月订单总金额

然后来直接查询:

月订单总金额

..

可以看到生成的 MQL:

SELECT ORDERS.月总订单金额 () AS \"月订单总金额 \" ON 年月 AS 年月 FROM ORDERS BY 订单日期 #年月

自动按照订单日期的年月层次做分组汇总。

有些复杂计算通过定义指标也可以轻松完成,比如要查询:

大订单数量

所谓大订单是指订单金额超过最大金额 50% 的订单,可以创建这样的指标:

..

指标函数:(x=?1.max( 订单总金额)/2,?1.count(订单总金额 >=x))

指标词:大订单数量, 大单数量

有了指标来做查询实验:

..

MQL:SELECT ORDERS. 大订单数量 () AS \"大订单数量 \" FROM ORDERS

指标词和其他词一样,都可以混合使用,比如查询:

2023 年的大订单数量

..

上面的指标函数使用的是 SPL 语法,具体可以参考姿料。

还可以定义 SQL 型指标,采用 SQL 语法。比如定义指标客单价:

..

SQL 指标写在 DQL 计算式上,表达式为:SUM(O_TOTALPRICE)/COUNT(O_CUSTKEY)

注意这里的字段要用 DQL 元数据中的字段名。

查询:去年客单价

..

MQL:

SELECT ORDERS. 客单价 ()AS \"客单价 \" FROM ORDERS WHERE ( 订单日期 #年 =year(ADDYEARS(now(),-1)))

5 LLM 辅助生成词典

除了手动创建词典,还提供了一些自动化的辅助生成词典功能。

5.1 获取数据库信息

工具 - 初始数据库信息,可以获得元数据描述的数据结构。

..

5.2 手动修改数据库信息

上面获得的是初始的表名字段名等是源名称,其中可能存在英文、简写等,表述不明确,需要手动添加中文名。数值类的字段,有可能需要指明单位。表信息后面的维度信息,同样需要添加中文名。如果维度中的常数词在生成 nlq 时需要自动处理,需要写明维度值和查询时使用值的对应情况(如果维对应某个实际的表)。修改后的信息:

1. 区域 (REGION), 字段包括:区域编号 (R_REGIONKEY): 主键, 数值. 区域名称 (R_NAME): 字符串.

2. 国家 (NATION), 字段包括:国家编号(N_NATIONKEY): 主键, 数值. 国家名称(N_NAME): 字符串. 所在区域(N_REGIONKEY): 外键, 与 REGION(R_REGIONKEY) 相关联, 数值.

3. 零件 (PART), 字段包括:零件编号 (P_PARTKEY): 主键, 数值. 名称 (P_NAME): 字符串. 制造商名称 (P_MFGR): 字符串. 品牌 (P_BRAND): 字符串. 零件类型 (P_TYPE): 字符串. 尺寸规格 (P_SIZE): 数值. 包装方式 (P_CONTAINER): 字符串. 零售价 (P_RETAILPRICE): 数值.

4. 供应商 (SUPPLIER), 字段包括:供应商编号(S_SUPPKEY): 主键, 数值. 名称(S_NAME): 字符串. 地址(S_ADDRESS): 字符串. 所在国家(S_NATIONKEY): 外键, 与 NATION(N_NATIONKEY) 相关联, 数值. 联系电话(S_PHONE): 字符串. 账户余额(S_ACCTBAL): 数值.

5. 零件供应商 (PARTSUPP), 字段包括:零件编号(PS_PARTKEY): 主键, 外键, 与 PART(P_PARTKEY) 相关联, 数值. 供应商编号 (PS_SUPPKEY): 主键, 外键, 与 SUPPLIER(S_SUPPKEY) 相关联, 数值. 可用供应量(PS_AVAILQTY): 数值. 供应成本(PS_SUPPLYCOST): 数值.

6. 客户 (CUSTOMER), 字段包括:客户编号(C_CUSTKEY): 主键, 数值. 名称(C_NAME): 字符串. 地址(C_ADDRESS): 字符串. 所在国家(C_NATIONKEY): 外键, 与 NATION(N_NATIONKEY) 相关联, 数值. 电话(C_PHONE): 字符串. 账户余额(C_ACCTBAL): 数值. 市场细分(C_MKTSEGMENT): 字符串.

7. 订单 (ORDERS), 字段包括:订单编码(O_ORDERKEY): 主键, 数值. 客户(O_CUSTKEY): 外键, 与 CUSTOMER(C_CUSTKEY) 相关联, 数值. 订单状态(O_ORDERSTATUS): 字符串. 订单总金额(O_TOTALPRICE): 数值. 订单日期(O_ORDERDATE): 日期时间. 订单优先级(O_ORDERPRIORITY): 字符串. 业务员(O_CLERK): 字符串. 发货优先级(O_SHIPPRIORITY): 数值.

8. 订单明细 (LINEITEM), 字段包括:订单(L_ORDERKEY): 主键, 外键, 与 ORDERS(O_ORDERKEY) 相关联, 数值. 零件 (L_PARTKEY): 主键, 外键, 与 PARTSUPP(PS_PARTKEY) 相关联, 数值. 供应商 (L_SUPPKEY): 主键, 外键, 与 PARTSUPP(PS_SUPPKEY) 相关联, 数值. 序号(L_LINENUMBER): 数值. 订购数量(L_QUANTITY): 数值. 总价(L_EXTENDEDPRICE): 数值. 折扣率(L_DISCOUNT): 数值. 税率(L_TAX): 数值. 退货标记(L_RETURNFLAG): 字符串. 发货状态(L_LINESTATUS): 字符串. 发货日期(L_SHIPDATE): 日期时间. 承诺到货日期(L_COMMITDATE): 日期时间. 实际到货日期(L_RECEIPTDATE): 日期时间. 发货指示(L_SHIPINSTRUCT): 字符串. 运输方式(L_SHIPMODE): 字符串.

维度列表: 1.(REGION) 自动处理 R_REGIONKEY[R_NAME], 2.(NATION) 自动处理 N_NATIONKEY[N_NAME], 3.(PART) 自动处理 P_PARTKEY[P_NAME], 4.(SUPPLIER) 自动处理 S_SUPPKEY[S_NAME], 5.(CUSTOMER.C_CUSTKEY) 自动处理 C_CUSTKEY[C_NAME], 7.(年) 不自动处理, 8.(季度) 不自动处理, 9.(年月) 不自动处理, 10.(月) 不自动处理, 11.(星期) 不自动处理, 12.(日) 不自动处理, 13.(日期) 不自动处理, 14.(行业) 不自动处理, 15.(订单状态) 不自动处理, 16.(订单优先级) 不自动处理, 17.(退货标记) 不自动处理, 18.(发货状态) 不自动处理, 19.(运输方式) 不自动处理, 20.(品牌) 不自动处理, 21.(零件类型) 不自动处理, 22.(包装方式) 不自动处理

5.3 使用 LLM 调整词典内容

将数据库信息输入大模型,通过 AI 分析,初步分析词典结构,包括表的别名、字段别名、字段簇和动词的使用、默认字段簇的设定等。提示词参考如下:

数据库信息如下:

1. 区域 (REGION), 字段包括:区域编号 (R_REGIONKEY): 主键, 数值. 区域名称 (R_NAME): 字符串.

2. 国家 (NATION), 字段包括:国家编号(N_NATIONKEY): 主键, 数值. 国家名称(N_NAME): 字符串. 所在区域(N_REGIONKEY): 外键, 与 REGION(R_REGIONKEY) 相关联, 数值.

……

维度列表: 1.(REGION) 自动处理 R_REGIONKEY[R_NAME], 2.(NATION) 自动处理 N_NATIONKEY[N_NAME],

……

现在以此为基础生出一份中文字典,按表的顺序排列。字典中需要信息如下(下文中的举例供参考,最终需要根据上述数据结构生成):

1、表信息,包括:表原始名称,包括表本身的名称(通常为英文)和表名标记(通常为中文),表名和标记可能相同,如果缺少其中之一则认为两者相同;表可能在汉语查询中使用的别名,如员工表,别名可能是“员工、雇员、员工信息”之类,别名列表中包括标记本身。同一个表或同一个字段的别名不能重复,但不同的字段或者不同的表的别名可能重复。

2、表内各个字段的信息,包括:字段原始名称,包括英文和中文,英文名中包含本表的英文名,如 EMPLOYEE.NAME;字段可能使用的别名,如出生日期,可能查询时用“生日”;字段类型,为数值、日期时间、布尔值、字符串这 4 种之一。在这些信息中,“可能使用的别名”在 5 个左右即可,需要比较常用的情况,避免错别字和太个性化的别名。

3、查询本表可能需要用到的外键表的字段,如查询员工时,可能需要查询“河北省员工”,需要用到外键表“城市”中的“省份”字段,这种可能用到的外键表字段,写在表内字段后面,英文名包含本表和外键表的英文名,如 EMPLOYEE.HOMECITY.PROVINCE。外键表字段同样需要可能使用的别名。

4、表内可能使用的字段组合,称为“字段簇”。包括:组合名称,组合内包含哪些字段,这里的字段用字段中文名表示,字段簇中的字段可以是表的基本字段,也可以是步骤 3 中的外键表字段。字段组合能够比较完整地表现一个信息,如“发货”字段簇,可能包含“发货日期,发货城市,发货城市名称,发货省”。同一个字段可能出现在多个字段簇中。

5、首先,每个表需要一个默认的字段簇,其中包含表中最关键的字段作为“标记字段”,标记字段中要包含主键。另外,默认字段簇中还要包括一些有可能在查询中不指定字段名,而直接查询的字段。如,“2025 年 订单”,这就需要订单的默认字段簇中包含一个能对应日期的字段,对于订单而言这通常会是“签单日期”,又如“河北省 员工”,这就需要员工的默认字段簇中,包含对应“省份”的字段。默认字段簇位于字段簇列表的最前方,名称与其它字段簇保持一致。

6、查询时,过滤条件中可能用一些动词表示条件对应的字段组合,在字段组合后面列出可能使用的动词。动词信息包括动词名称,以及左侧和右侧对应的字段簇。如动词“发往”,左侧是发货信息,右侧是收货信息,那么左字段簇是“发货”,右字段簇是“收货”。在查询时,可以用“2025 年 发往 河北”,通过字段簇,表示过滤条件相当于“发货日期 2025 年,收货省河北”。也就是说,在过滤条件中使用动词,就不必再指明相关的字段了。如果动词仅涉及单一字段,也需要在步骤 4 中将其设为单字段的字段簇,如“生于”右侧可能只会涉及“生日”,如“生于 6 月”相当于“生日 是 6 月”。动词可能有多个名称,如“发往”“发给”“发到”等,动词的多个名称之间用半角逗号隔开。动词只要符合限定字段簇的情况即可,词性不一定非是动词,但不能是关联短语,比如不能是“从……发往”这样的词。一个字段簇可能与多个动词匹配,如“生于”右字段簇为“生日”,“出生”左字段簇为“生日”等。注意,这里的动词并不包括单纯与字段值比较的情况,如不包括“编号为”,“金额大于”,“籍贯是”这种包含字段或别名本身的表述。动词关联到某个字段簇时,常常相当于对应多个字段,在查询时不一定多个字段的过滤条件同时使用,可以使用部分条件,也可能查询条件中只包含单侧的字段簇。动词关联到的字段簇,簇中的各个字段都是有可能在查询时涉及的。如果某种条件既可能出现在动词左侧,又可能出现在动词右侧,这种情况动词的左簇和右簇可以相同。

7、字段组合还可能有一种作用是查询时简化语句,如“发货 日期 城市名称 省”,能够查询到“发货日期,发货城市名称,发货省”。这种使用情况称为“簇词”,簇词会对应一个字段簇,或者一个外键字段。如果对应的是字段簇,需要在涉及的字段如“发货城市”中,增加“城市”的簇词用别名。同一表中,不同字段的簇词用别名可以相同,在查询时用不同的簇词做区分。

8、查询中可能根据维度汇总,如“按月……”“每个员工……”等等。数据库信息中列出了可能用到的维度,但在查询时,可能使用不同的别名,如“每位雇员”等等。类似表的别名,也列出每个维可能使用的别名,别名允许重复。

分析得到的数据库中文字典,按表的顺序排列。

5.4 LLM 根据例子再次完善词典

阅读初步分析结果后,由于是按照通用化的使用情况分析得到的信息,因此不一定符合使用实际场景。下面,按照实际使用的需求,准备一批查询中可能使用的例句,以便 LLM 对中文词典进一步调整。

准备例句的目的是:

①为了校准词典中可能缺少的表词、字段词等内容;

②完善词典中字段簇等的设定;

③添加一些可能需要的通过计算得到数据,如用身高和体重计算得到的 BMI 指数等;

④如果调整中发现其它问题,可以要求 LLM 列出以作参考。

例句中要求包含比较齐全的表或者字段的别名,如对表或字段的习惯性口语化的称呼,用来补充表词字段词等信息;还需要查询的一些典型需求等。把准备的例子再喂给 LLM,提示词如下:

下面将列出一批想用来查询的语句,请根据这些例句做以下事情:

1、完善词典,比如词典中缺少的字段别名、表别名、维别名、动词等,或者字段簇设置不合理的情况。需要注意的是,对于日期型的数据,在实际查询中可以对其分量执行统计,如“1 月出生 员工”,“每年 总销售额”等,无需针对分量添加字段。

2、完善字段簇的设定,如未说明字段名时使用过滤条件,对应的字段是否在默认字段簇中;动词关联的字段簇是否缺少字段等。

3、如果查询的某些字段目前表中没有,但是可以通过计算获得,那么增加字段词说明,对应的英文名用公式表示,公式中的字段名和前面的字段名要求一样,也要求包含表名。

4、根据例句的格式,在词典中每个表信息后面,造出一些本表相关的例句,包括整表查询,某些字段的查询等。这些例句中可以用已有的,但更多的应该根据词典创造出来。其中可能使用的不同字段词;用默认字段簇的字段过滤数据的查询语句;用各个动词的不同使用情况等。如果查询涉及多个表,例句放在所有表后面。

5、在整理好的词典最后,总结出还有哪些例句的查询可能存在问题。

下面是例句:

1. 零件编号、名称、制造商、零售价

2. 供应商名称、联系电话、所在国家、账户余额

3. 订单号、下单日期、客户名称、订单总金额

4. 零件名称、品牌、尺寸、包装

5. 客户名称、电话、市场细分、所在区域

6. 订单明细中,零件名称、订购数量、总价、折扣率

7. 发货日期在 2025 年 3 月以后的订单明细

8. 承诺到货日期早于实际到货日期的订单行

9. 折扣后金额大于 1000 元的明细(总价 * (1- 折扣率))

10. 未退货的订单明细(退货标记为 'N')

11. 延迟发货的订单:实际到货日期晚于承诺到货日期

12. 订单状态为“已发货”且运输方式为“航空”的明细

13. 客户“王伟”的所有订单

14. 供应商名称包含“科技”的零件供应信息

15. 品牌为“Brand#12”且零件类型以“STANDARD”开头的零件

16. 每个国家的客户数量和账户余额平均值

17. 各区域的订单总金额和订单数量

18. 零件供应成本最低的 5 个供应商

19. 订购数量最多的 10 个零件(按总订购量排序)

20. 2024 年每月订单总额趋势

21. 每个市场细分中,账户余额最高的 3 位客户

22. 订单金额超过 5 万元的订单,及其客户名称

23. Supplier#000000001 卖给 Customer#000147017 的 订单明细

24. 制造商“Manufacturer#1”生产的零件,在 2025 年第一季度被订购的总数量

25. 没有下过任何订单的客户

26. 既订购了“螺丝”又订购了“螺母”的订单

27. 下单日期在周一或周二的订单

28. 每个业务员经手的订单数量和平均订单金额

29. 去年每个季度的发货明细数量

30. 承诺到货日期 - 发货日期超过 7 天的明细,标记为“超期”

5.5 将结果转换为词典文件

将根据例子完善的词典内容输出为 JSON 格式,仍然使用 LLM 来完成。

词典经过调整后,将其输出为 json 格式,以便用代码读取词典的信息,辅助生成 nlq 文件。对于输出格式,可以给出例子以便符合需要。提示词参考如下:

现在,请将中文词典结果用 json 格式返回,格式如下:

{
  "tables": [
    {
      "tableName": "TABLENAME",
      "tableAliases": ["表别名1", "表别名2",…],
      "fields": [
        {
          "fieldName": "TABLENAME.FIELD1",
          "fieldChinese": "字段名",
          "fieldType": "字段类型",
          "aliases": ["字段别名1", "字段别名2",…],
          "computedFormula": "字段计算式(如果有)"
        },
        …
      ],
      "foreignKeyFields": [
        {
          "fieldName": "外键字段名",
          "fieldType": "外键字段类型",
          "fieldChinese": "外键字段中文名",
          "aliases": ["外键字段别名1",…]
        },
        …
      ],
      "fieldClusters": [
        {
          "clusterName": "簇名",
          "fields": ["字段中文名1", "字段中文名2", …]
        },
        …
     ],
      "verbs": [
        {
          "verb": "动词1",
          "leftCluster": "左簇名(如果有)",
          "rightCluster": "右簇名(如果有)"
        },
        …
      ]
    },
    …
  ],
  "dimensions": [
    {
      "dimName": "维名",
      "dimAliases": ["维别名1", "维别名2",…],
      "auto": true,
      "dimKey": 维对应表主键(如果有),
      "dimName": 维对应值(如果有)
    },
    …
  ],
}

其中维名是维的源名称,即最开始在括号里的名称。

这里 json 中的标签名称,是对应NLQDictUtils中的解析方法的,如果自行解析可根据需要修改。

然后将输出的 JSON 导入 NLQ 汉语查询 IDE 中,新建词典,选择“加载 JSON”,导入 LLM 生成的 JSON 文件。

..

5.6 手动调整词典

导入 JSON 生成的词典内容还可能有不完善的地方,这时就需要手动修改了,可以根据需要调整。

..

这个调整过程就跟前面手动创建词典完全一样了,不再赘述。

5.7 总结

使用 LLM 辅助生成词典步骤大概如下:

1. 了解数据表信息:掌握各数据表的内容,包括字段含义、数值单位、维信息(维名称、源字段名)等。可以通过 IDE 辅助从 DQL 连接中获取数据表相关的基础信息,再手工完善。

2. 初步分析生成词典:将表信息整理后提交 AI,根据通用查询习惯分析并输出词典结构,包括字段词、实体词、字段簇、动词及维词。

3. 调整完善词典:结合实际查询需求(如习惯用语、别名、计算字段),提供例句等更多信息,要求 AI 优化词典;可同时准备测试例句。

4. 输出 JSON 格式词典:将调整后的词典按指定格式输出为 JSON 串,并保存到 JSON 文件(UTF-8 编码)。

5. 生成 nlq 词典文件:通过 IDE 辅助解析 JSON,结合从模板文件中复制的通用性信息(包括量纲、无效词、聚合词等),生成 nlq 文件。

有了 LLM 辅助,制作 NLQ 词典就更简单了。

NLQ 词典下载:

tpch.nlq.rar

6 部署

除了在 IDE 中进行查询实验,正式应用时需要将其部署到 WEB 上。

6.1 配置 DQL&NLQ JDBC 连接

修改 raqsoftConfig.xml,配置 DQL 和 NLQ 数据源

<DB name="DQL4TPCH">

<property name="url" value="jdbc:datalogic://?home=mql_home&amp;lmd=services/tpch0323/conf/tpch0323.lmd&amp;dct=&amp;db.url=jdbc:hsqldb:hsql://127.0.0.1/tpch&amp;db.driver=org.hsqldb.jdbcDriver&amp;db.user=sa&amp;db.password=&amp;db.type=13"/>

<property name="driver" value="com.datalogic.jdbc.LogicDriver"/>

<property name="type" value="0"/>

<property name="user"/>

<property name="password"/>

<property name="batchSize" value="1000"/>

<property name="autoConnect" value="false"/>

<property name="useSchema" value="false"/>

<property name="addTilde" value="false"/>

<property name="caseSentence" value="false"/>

</DB>

<DB name="NLQ4TPCH">

<property name="url" value="jdbc:datalogic:nlq://?home=mql_home&amp;nlq=nlqTPCH"/>

<property name="driver" value="com.nlq.datalogic.jdbc.NLQDriver"/>

<property name="type" value="0"/>

<property name="user"/>

<property name="password"/>

<property name="batchSize" value="1000"/>

<property name="autoConnect" value="false"/>

<property name="useSchema" value="false"/>

<property name="addTilde" value="false"/>

<property name="caseSentence" value="false"/>

</DB>

6.2 词典配置

修改 classes\nlqConfig.xml

<NLQ name ="nlqTPCH">

<!-- 在 raqsoftConfig.xml 中配置好的 DBConfig 名称 -->

<DB>DQL4TPCH</DB>

<!-- nlq 文件路径,支持类路径和绝对路径 -->

<MetaData>web/webapps/demo/WEB-INF/files/dql/tpch0323.nlq</MetaData>

<!-- 当 NLQ 下配置了 RaqsoftConfig,以 NLQ 下的为准 -->

<RaqsoftConfig></RaqsoftConfig>

</NLQ>

6.3 Web 访问

http://localhost:6868/demo/raqsoft/dql/jsp/dqlSearch.jsp?dataSource=NLQ4TPCH

传递数据源名称

..

7 LLM 规范语句

通过词典虽然已经可以实现相当灵活程度的 Chat 查询,但对于千奇百怪的口语表达来说还不够。LLM 非常擅长将多变的汉语表达转换成符合 NLQ 要求的输入形式(规范文本)。所以扩展灵活性可以将 NLQ 与 LLM 结合使用,借助提示词完成从口语到规范文本的转换。

7.1 提示词

规范转换的提示词文件位于应用的classes 目录下,其中 nlq-prompt.md 是深度规范的完整提示词文件。该文件将所有转换规则、词典和范例整合在一起,一次性交由 LLM 处理,将用户的自然语言查询转换为系统可执行的规范查询文本。

nlq-prompt.md 的文件结构如下,可根据需要查阅对应章节:

  • 范式解释:定义了单表明细、单表聚合、主子实体、多维对齐汇总四种规范范式及其构成规则

  • 词典:包含字段词、实体词、维词、常数词、比较词、聚合词、宏词、指标等,所有规范文本只能使用词典中定义的词汇

  • 转换要求:包括强制范式套用、去除虚词、陈述句化、常数格式等规则

  • 例句:按范式分类的成功转换示例和无法转换示例,供 LLM 参考学习

如果要将系统切换到其他业务主题(如从 TPCH 切换到企业 ERP),通常只需要修改词典和例句部分即可。其中词典由程序自动加载和匹配,用户无需手动维护;因此,用户主要需要准备的是贴合新业务场景的例句,帮助 LLM 正确理解查询意图。

当 LLM 对某些查询的转换结果不符合预期时,可以通过补充例句的方式进行优化:

  1. 定位问题:检查转换结果是否归入正确的范式、条件顺序是否正确

  2. 在 nlq-prompt.md 的“例句”章节对应范式下,按输入:xxx → 输出:xxx 格式补充新例句

  3. 重新执行转换,测试验证

例如,发现“各订单优先级下,已完成订单数和未完成订单数”被错误转换,可补充:输入:各订单优先级下,已完成订单数和未完成订单数 → 输出:订单优先级 (已完成 订单 数) (未完成 订单 数)

由于深度规范将全部转换逻辑放在一个提示词中,LLM 处理速度较慢,NLQ 另外设计了不采用深度思考的分步规范方案。该方案将转换过程拆解为多个独立的子任务,每个子任务对应一个专门的提示词(C1~C6.md 存放在 classes 目录下),LLM 按顺序执行这些子任务,逐步完成表选择、范式识别、语句生成、验证、不支持判定、条件整理等环节。分步执行降低了单次调用的复杂度,提升了响应速度。

7.2 LLM 配置

在页面中增加三个核心参数即可接入 LLM:

const llmUrl = "https://api.deepseek.com/chat/completions";
const requestJSON = `{"max_tokens":4096,"temperature":1.0,"stream":false,"model":"${thinking==1?"deepseek-reasoner":"deepseek-chat"}","enable_thinking":${thinking==1}}`;
const keyFile = "deepseek.txt";

llmUrl:填写所选大模型的 API 接口地址。

requestJSON:配置 LLM 的调用参数,如模型名称等。

keyFile:指定存储 API 密钥的本地文本文件名(如 deepseek.txt),将对应服务的密钥存入该文件即可。

这里配置了 deepseek 的 API 接口地址,配置了模型为 deepseek-chat 模型,以及最大 token 量等参数,并将自己的 APIKey 填入 deepseek.txt 文档,放在 WEB-INF 目录下。

深度规范和分步规范各有优势,前者更准后者更快,两者可以结合使用。配置 LLM 后,NLQ默认会使用分步规范,转换后的规范文本如果发现不够准确还可以进步“深度规范”,从而更好完成文本规范。

分步规范:

..

深度规范:

..

分步规范与深度规范入口:

..