润乾 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 辅助

DQL 元数据和 NLQ 词典还可以借助大模型辅助生成。

5.1 配置 LLM

在 DQL IDE 中,配置 LLM

..

添加 LLM API 信息:

..

这里选择了 deepseek,并使用推理模型。

5.2 生成 DQL 元数据

配置 LLM 后,还可以进一步选择哪些流程使用 LLM 辅助。

..

确定后等待 LLM 生成结果就可以了。

..

注意事项

鉴于 LLM 的幻觉问题,生成后的元数据要认真检查,可能需要手动修改的内容:

1. 少量表名和字段名翻译不合适需要修改

2. 部分维名需要修改(如将 区域 _ 区域 ID -> 区域)

3. 少量外键缺失需要添加

4. 日期外键缺失(所有日期字段均未指向日期假表)

此外还存在需要重新生成的情况:

1. LLM 生成的元数据如果出现多余的维词时,需要重新生成,比如

..

2. 将实体表识别为假表,本例中多见地区和国家两个表。此时可以重新生成或手动修改。

有了 LLM 辅助,生成 DQL 元数据效率大幅提升。

5.3 生成 NLQ 词典

词典比较复杂,LLM 生成的时候采用了更多的分步流程,期间需要多次手动参与。

5.3.1 表和字段、维度名称分析

工具 - 初始数据库信息,可以获得元数据描述的数据结构、默认量纲等信息。

..

上面是数据库相关信息,每一步分析都是以此为基础的。

接下来要求 LLM 完成下面的任务:

(复制上面的数据库信息放到这里)

# 上面是数据库信息描述

# 任务

- 现在以此为基础生出一份中文字典,按表的顺序排列,字典中需要的信息由下列系列任务列出,需依次执行,不能跳步。

## 任务1

- 处理表信息。数据库信息中,开头在括号中提供了表的原始名称(通常为英文),可能会有表标题(通常为中文),如果没有表标题或者表标题为英文,需要生成一个中文表标题。在表标题基础上,需要生成表可能在汉语查询中使用的别名,如员工表,别名可能是“员工、雇员、员工信息”之类,按照通用程度降序排列。各个表的别名不能重复,别名中不允许包含符号或空格。

## 任务2

- 处理表内各个字段的信息。数据库信息中,提供了字段原始名称(通常为英文),可能会有字段标题(通常为中文),如果没有字段标题或者字段标题为英文,需要生成一个中文字段标题。原始名称需要整理为包含本表的原始名,如 EMPLOYEE.NAME。在字段标题基础上,生成字段可能在汉语查询中使用的别名,列出常见同义表达,一般不超过5个,按通用程度降序排列;若实际常用别名不足,不硬凑数量。字段类型,为数值、日期时间、布尔值、字符串这4种之一。

- 查询时可能需要用到沿着一条或多条外键路径可达的任意表中的扩展字段。应遵循外键关系链,将关联对象的核心描述字段(名称、类别、日期等)以及其所在的地点信息(城市和省份,如有)作为本表的可用字段。引入的字段须按该关联在当前表中的业务角色重新拟定中文标题和别名,不得直接搬用源表字段名称(例如源自客户地址的信息在订单中应体现为“收货地”相关称谓)。字段原始名称需包含完整的关联路径(如 A.外键B.字段X A.外键B.外键C.字段Y),并给出中文标题和别名。可用的扩展字段添加在每个表的普通字段之后,添加后,使用时与普通字段相同。

- 参考各个表的数据示例,标记字段中使用的单位。要求单位在量纲信息中,注意某一类信息中均包含多个单位,如rmb分类中,提供了“元”“万元”等多个单位。如果单位在查询习惯中经常省略,如单位是“个”,不必添加。

## 任务3

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

## 任务4

- 设置表和字段的别名后,可以使用表名和别名执行汉语查询了。生成10句左右的例句,格式为“[实体] [字段列表]”,其中实体或者字段列表可省略,实体名为表的某个别名,字段列表由若干字段(包括扩展字段)的别名构成。如“员工”,“商品名称 单价 厂家”等。这种类型的查询,实体和字段列表必须在同一个表中。

- 在查询语句最前面可以添加过滤条件,格式为“字段A 条件A [字段B 条件B]……”,条件格式为“[比较符] 常数”,比较符用中文的“大于”“等于”“大于等于”等标准描述,相等的比较可以省略比较符。常数的类型需要和字段匹配,如果字段是字符串类型时常数需用双引号括起来,如:省份 “辽宁” 城市。常数为日期时,可以使用日期的分量,如:签单日期 2025 订单号 金额。常数为数值且字段有单位时,单位不要省略。再生成10句左右带过滤条件的查询语句。过滤条件中的常数参考数据示例。

# 输出1

- 数据库信息中各个部分按原顺序保留,最前方是量纲和单位,下面是表和字段信息,最后是维度列表。即使无变化也不能省略。

- 在表和字段信息中,表名和字段名中都需要增加中文标题和别名。标题为一个词,标题后在括号内列出实际表名。别名为多个,别名中不包含标题。字段信息中,除了字段标题和字段别名外,还有可能增加单位,记为“单位:xx”。数据示例全部保留。

# 输出2

- 生成的例句,排列在一起,格式如下:

例句A1:……

例句A2:……

……

- 编号从A1A20

5.3.2 生成不完全词典文件

将上述输出 1 转成 JSON 格式,提示词:

(复制前面的输出 1 放到这里)

# 上面是数据库信息描述

# 任务1

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

{

"tables": [

{

"tableName": "TABLENAME",

"tableChinese": "表中文名",

"tableAliases": ["表别名1","表别名2",],

"fields": [

{

"fieldName": "TABLENAME.FIELD1",

"fieldChinese": "字段全名",

"fieldType": "字段类型",

"fieldUnit": "字段单位(如果有)",

"aliases": ["字段别名1","字段别名2",],

"computedFormula": "字段计算式(如果有)"

},

],

"extendFields": [

{

"fieldName": "扩展字段全名",

"fieldType": "扩展字段类型",

"fieldUnit": "扩展字段单位(如果有)",

"fieldChinese": "扩展字段中文名",

"aliases": ["扩展字段别名1",]

},

],

"fieldClusters": [

{

"clusterName": "簇名",

"fields": ["字段中文名1","字段中文名2", ],

"markFields": ["标记字段中文名1","标记字段中文名2", ]

},

],

"verbs": [

{

"verb": "动词1",

"leftCluster": "左簇名(如果有)",

"rightCluster": "右簇名(如果有)"

},

]

"clusterWords": [

{

"wordName": "簇词名",

"clusterName": "对应的簇名或外键名",

},

],

"indices": [

{

"indexName": "指标名",

"clusterName": "对应字段簇(如果有)",

"dims": ["固有维度1",](如果有),

"function": "函数表达式"

},

],

},

],

"dimensions": [

{

"dimName": "维名",

"dimAliases": ["维别名1","维别名2",],

"auto": true,

"dimKey": 维对应表主键(如果有),

"dimNameField": 维对应值(如果有)

},

],

}

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

IDE菜单中新建词典,生成方式选择加载JSON,粘贴前面得到的json文字(也可以选择打开json文件),分析后可以生成新的词典文件。

..

此时,目前设置的字段名称和别名,已经在词典中生成:

..

5.3.3 手动调整词典内容

上面的步骤完成后,词典内容需要仔细校验。发现问题需要手动修改,可能会存在:

1. 词名称不准确,需要修改(如:维词 国家 ID-> 国家)

2. 扩展字段的广义字段表达式不准确,需要修改(如:客户. 国家. 区域. 区域名称 -> 客户. 国家 ID. 区域 ID. 区域名称)

3. 带条件的实体需要自行添加

所有检查和调整完成后我们得到了一个初步的 NLQ 词典,这时可以使用第一步中生成的例句进行查询了。

5.3.4 字段簇分析

IDE的菜单中选择工具->重新获取数据库信息,此时获得的信息是在上一步调整好的nlq文件基础上,综合当前连接数据库的信息得到的。注意:在已有nlq文件的基础上继续分析时,本流程以外自行添加的属性、设置等可能丢失,如带过滤的实体,已有字段簇的主键等信息。

在数据库信息之后,说明字段簇分析任务,LLM 提示词如下:

# 上面是数据库信息描述

# 任务

- 现在以此为基础整理各个表中的字段组合,按表的顺序排列,需要的信息由下列系列任务列出,需依次执行,不能跳步。

## 任务1

- 表内可能使用的字段组合,称为“字段簇”。包括:组合名称,组合内包含哪些字段,这里的字段用字段标题表示,字段簇中的字段可以是表的基本字段,也可以是已定义的外键表字段。字段组合能够比较完整地表现一个信息,如“发货”字段簇,可能包含“发货日期,发货城市,发货城市名称,发货省”。同一个字段可能出现在多个字段簇中,字段簇中的字段数一般多于1个。字段簇中不允许包含“同类”字段即使用同一个维度的字段,如不能包含两个日期类字段,也不能包含两个城市编码字段等。

- 每个字段簇从其中的字段列表中,选择至少一个设为“标记字段”。标记字段中应该包含字段簇中使用的主键类字段,也可以包括其它代表字段簇最核心信息的字段。如“发货”字段簇中的标记字段可以选择“发货日期,发货城市”。

## 任务2

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

## 任务3

- 设置默认字段簇后,可以在汉语查询中基于默认字段簇中的字段使用过滤条件,过滤条件中不必指明对应字段。生成15句左右这样的例句,格式为“条件A [条件B]……[实体] [字段列表]”。其中实体或者字段列表可省略,实体名为表的某个别名,字段列表由若干字段的别名构成。条件格式为“[比较符] 常数”,比较符用中文的“大于”“等于”“大于等于”等标准描述,相等的比较可以省略比较符。常数的类型需要和默认字段簇中的某一个或多个字段匹配,如果字段是字符串类型但在维度中使用,则不必加引号,如:辽宁 客户。常数为日期时,可以使用日期的分量,如:2025 订单号 金额。常数为数值且字段有单位时,单位不要省略。这种类型的查询,实体和字段列表必须在同一个表中。过滤条件中的常数参考数据示例。

# 输出1

- 数据库信息修改后的结果,数据库信息中各个部分按原顺序保留,最前方是量纲和单位,下面是表和字段信息,最后是维度列表。

- 在表和字段信息中,表的字段描述和数据示例之间增加可用的字段簇,其中默认字段簇在最前面。数据示例全部保留。数据示例全部保留。

# 输出2

- 生成的例句,排列在一起,格式如下:

例句B1:……

例句B2:……

……

- 编号从B1B15

得到的结果可以再按前面的步骤转换成 JSON 后重新生成词典文件进行调整。接下来都是这样的过程,重复内容不再赘述。

5.3.5 动词分析

生成动词,可以在上一步的词典基础上“重新获取数据库信息”,也可以直接使用“字段簇分析”的输出 1 结果。在数据库信息之后,说明动词分析任务,提示词:

# 上面是数据库信息描述

# 任务

- 现在以此为基础整理各个表中的字段组合,按表的顺序排列,需要的信息由下列系列任务列出,需依次执行,不能跳步。

## 任务1

- 查询时,过滤条件中可能用一些动词表示条件对应的字段组合,在字段组合后面列出可能使用的动词。动词信息包括动词名称,以及左侧和右侧对应的字段簇。

- 左右簇的语序对应规则:定义动词时,必须确保左右字段簇与自然查询语句中的条件位置一致——查询语句中置于动词之前的条件,其对应字段必须属于左簇;置于动词之后的条件,其对应字段必须属于右簇。左簇或右簇可以为空,表示动词仅单侧接受条件。设定左右簇前,应先构思典型的查询句式,确认动词在句中的位置符合汉语表达习惯,再据此分配字段簇方向。

- 动词形式规则:动词必须是完整的独立词形,不允许包含省略号、破折号等任何占位符,不允许使用依赖前后嵌词才能表意的关联结构(如“由…发起”)。

- 动词可能有多个名称,如“发往”“发给”“发到”等,动词的多个名称之间用半角逗号隔开。

- 一个字段簇可能与多个动词匹配。动词关联到某个字段簇时,常常相当于对应多个字段,在查询时不一定多个字段的过滤条件同时使用,可以使用部分条件,也可能查询条件中只包含单侧的字段簇。动词关联到的字段簇,簇中的各个字段都是有可能在查询时涉及的。如果某种条件既可能出现在动词左侧,又可能出现在动词右侧,这种情况动词的左簇和右簇可以相同。

***禁止情形***

- 动词不能仅表示单纯的字段值比较,如不能包含“字段名+比较词”的语义(例如“金额大于”“籍贯是”)。动词应表达一种有动作逻辑的关系,而非直接映射字段筛选。

- 动词不能使用“之前”“之后”这类不承载明确动作含义的时间介词。

- 同一个表中,动词不能重复,也不能和字段的标题或别名重复。

## 任务2

- 设置动词后,可以在汉语查询的过滤条件中使用动词,通过动词关联的字段簇寻找对应字段。生成15句左右使用动词过滤的例句,格式为:

[条件左A] [条件左B]…… 动词 [条件右A] [条件右B] …… [实体] [字段列表]

- 其中实体或者字段列表可省略,实体名为表的某个别名,字段列表由若干字段的别名构成。

- 条件格式为 [比较符] 常数,比较符用中文的“大于”“等于”“大于等于”等标准描述,相等的比较可以省略比较符。常数的类型需要和默认字段簇中的某一个或多个字段匹配。常数的位置必须严格按动词左右簇的语序放置,不可颠倒。

- 常数为日期时,可以使用日期的分量,如:19905 之前 出生 员工。常数为数值且字段有单位时,单位不要省略。

- 过滤条件中的常数参考数据示例。实体和字段列表必须在同一个表中。

# 输出1

- 数据库信息修改后的结果,数据库信息中各个部分按原顺序保留,最前方是量纲和单位,下面是表和字段信息,最后是维度列表。

- 在表和字段信息中,字段簇描述和数据示例之间增加可用的动词。数据示例全部保留。数据示例全部保留。

# 输出2

- 生成的例句,排列在一起,格式如下:

例句C1:……

例句C2:……

……

- 编号从C1C15

生成 JSON 并导入词典,或者直接进行下面的分析。

5.3.6 簇词处理

簇词分析提示词:

# 上面是数据库信息描述

# 任务

- 现在以此为基础整理各个表中的字段组合,按表的顺序排列,需要的信息由下列系列任务列出,需依次执行,不能跳步。

## 任务1

- 字段组合可用于查询简化,这种组合称为“簇词”。簇词对应一个字段簇或一个外键字段,用于在查询中分组引用或消歧。

- 簇词命名规则:簇词名称不得与任何表名、表别名、维度名重复。

- 簇词别名:簇词在查询中可以使用不同的名称,如簇词“上级”可以用“经理、领导、主管”等。别名不得与表名或表别名、维度名重复。

- 簇词定义规则(基于查询消歧需求):

簇词仅在查询需同时引用多个同名字段,且这些字段的别名存在冲突或易混淆时才需定义。冲突来源包括:表自身字段与外键关联字段同名,或表中不同分组的字段同名。

对于外键关联字段:若其引用字段的常用别名与当前表自身字段别名冲突,或查询习惯中经常需要同时引用并加以区分,则应定义簇词;否则可不定义。

对于表内字段:若不同字段簇包含相同别名,且查询中可能需要同时使用这些分组,则需为相应字段簇定义簇词;若各字段别名全局唯一可直接确定,则无需定义簇词。

## 任务2

- 生成10句左右使用簇词的例句,格式为:

[实体] [实体字段列表] [簇词1] [簇词1内字段列表] [实体字段列表] [簇词2] [簇词2内字段列表] ……

- 其中实体或者字段列表可省略,实体名为表的某个别名,字段列表由若干字段的别名构成。

# 输出1

- 数据库信息修改后的结果,数据库信息中各个部分按原顺序保留,最前方是量纲和单位,下面是表和字段信息,最后是维度列表。

- 在表和字段信息中,动词描述和数据示例之间增加可用的簇词。数据示例全部保留。

# 输出2

- 生成的例句,排列在一起,格式如下:

例句D1:……

例句D2:……

……

- 编号从D1D10

将输出结果转成 JSON 后生成词典。

以上步骤都完成后我们就得到了一个完整的 NLQ 词典。由于 LLM 的幻觉问题,建议每步生成词典检查,再基于调整后的词典进行下一步工作。

注意:带条件的实体建议在最终调整完的词典基础上手动添加。


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默认会使用分步规范,转换后的规范文本如果发现不够准确还可以进步“深度规范”,从而更好完成文本规范。

分步规范:

..

深度规范:

..

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

..