WorkBuddy 加持,一天上线八表关联智能问数
前言
本文完整记录了一次真实的智能问数(润乾 NLQ)上线全过程——从环境准备到最终验证通过,实际耗时约一个工作日。我们使用 WorkBuddy 作为“执行 Agent”,直接操作本机润乾 DQL/NLQ 引擎与 HSQLDB 中的 TPCH 数据集,从元数据生成、汉语词典构建、LLM 提示词编写、数据源配置到最终 Web 查询页面,一路打通。最终,像“各地区的订单金额总和”“1996 年每月的订单数”这类自然语言问题,均成功返回真实查询结果。
与常见的单表 Demo 不同,TPCH 的多表外键穿越(订单→客户→国家→地区,四层关联)是智能问数应用中的难点。本文会剖析这类跨表关联在语义层中的建模方法,并展示润乾 NLQ 在处理复杂 JOIN 上的天然优势。
当前 Demo 尚未接入用户角色与数据权限,但润乾 DQL 内核层已内置行 / 列级权限控制能力,可通过关联表作为条件进行细粒度过滤,后续配上角色即可平滑扩展。本文重在展示核心链路的搭建方法,权限部分将在后续文章中补充。


最省事的读法:直接跳到第九节,复制一段总提示词给 WorkBuddy,就能复现整个过程。
项目 |
内容 |
目标读者 |
熟悉 SQL 、想要快速落地 NLQ 智能问数 Demo 的后端 / 数据 / BI 工程师 |
前置知识 |
SQL 基础;对外键、主子表、维表与事实表有基本概念 |
技术栈版本 |
润乾 DQL/NLQ (授权)、 HSQLDB 2.7.3 、 TPCH 标准数据集 |
预计阅读时间 |
约 15 分钟 |
一、为什么是润乾 NLQ,又为什么用 WorkBuddy
"智能问数"的目标是让业务人员用大白话提问,系统自动查出数据。市面上的方案大多非常繁重,不仅需要专业的 AI 团队,做模型训练、语义理解等工作常常要花费数周到数月的时间,完全达不到快速验证智能问数的目标。
润乾走的是另一条路——两段式 NLQ:
1. 第一段,大模型(LLM)把自然语言翻译成一种规范查询文本;
2. 第二段,润乾引擎解析规范文本、执行查询并返回结果。规范文本是结构化、受限的,SQL 是引擎按元数据自动生成的,所以既跑得通,又能保证 100% 准确率。
那 WorkBuddy 在这里扮演什么角色?相较于靠人机对话一步步操作的对话式 AI 只能"出主意",不能"动手"。WorkBuddy 是 Agent 形态,能直接读本机文件、连接数据库读表结构、写配置文件、编译运行 Java 验证程序、把跑通的产物回填,让用户快速搭建智能问数应用,一天搞定。
二、方案架构与成果物全景
一次完整的润乾 NLQ 上线,产出物可以归纳为 6 类文件,它们的分工如下表:
产物 |
作用 |
层 |
wb_tpch.lmd |
DQL 元数据:物理表结构 + 外键关系 + 维表定义 |
语义层 |
wb_tpch.nlq |
汉语词典:词表 + 字段视图 + 维 / 常数词 + 聚合词 |
语义层 |
wb_tpch_nlq_nspl.md |
LLM 提示词:自然语言 → 规范查询文本的翻译规则 |
大模型层 |
raqsoftConfig.xml |
注册 DQL / NLQ 两个数据源(驱动 + URL ) |
配置层 |
nlqConfig.xml |
把 NLQ 实例 wb_tpch 绑定到数据源与元数据 |
配置层 |
wb_tpch.jsp |
Web 查询页面(业务人员直接提问的入口) |
应用层 |
再加一个 VerifyNLQ.java,它是真连真查的验证程序——不是"看起来对",而是真的走 JDBC 驱动把 DQL 和 NLQ 都跑一遍、打印真实返回。整个链路是:
用户自然语言 → LLM( 提示词 .md → 规范查询文本
→ NLQ 引擎 (com.nlq.datalogic.jdbc.NLQDriver) → MQL 语义查询
→ DQL 引擎 (com.datalogic.jdbc.LogicDriver + 元数据 .lmd) → SQL
→ HSQLDB(TPCH 8 表) → 真实结果
三、TPCH 数据集:多表关联场景
本文选了 TPCH,它是 TPC 基准测试的标准决策支持数据集,典型的星型/雪花关联结构,8 张表:
表 |
中文 |
行数(本机实测) |
角色 |
REGION |
地区 |
5 |
维度(顶层) |
NATION |
国家 |
25 |
维度 |
SUPPLIER |
供应商 |
10000 |
维度 |
CUSTOMER |
客户 |
11572 |
维度 |
PART |
零件 |
10 |
维度 |
PARTSUPP |
零件供应 |
159750 |
事实(子) |
ORDERS |
订单 |
12380 |
事实(主) |
LINEITEM |
订单明细 |
59206 |
事实(子) |
它们之间的外键关系构成一个典型的多层关联网络:
· 订单 → 客户 → 国家 → 地区(四层穿越)
· 订单明细 → 订单、零件、供应商
· 供应(PARTSUPP) → 零件、供应商
· 供应商 → 国家 → 地区
多表关联给 NLQ 带来三个比单表难得多的问题:
1. 外键穿越:业务问"各地区订单金额",数据实际在订单表,但"地区"要沿 订单. 客户编码 → 客户. 国家编码 → 国家. 地区编码 → 地区. 地区名称 连续跳 4 层才能拿到。词典必须把这条"路径"精确描述清楚,少一跳、跳错一步就查不出。
2. 主子表:订单(主)和订单明细(子)是一对多。问"各客户订购金额"要落到明细表聚合,问"各客户订单数"又要落到订单表聚合——同一个"客户"维度,不同指标对应不同事实表,词典要能区分。
3. 跨表聚合:多个事实表都要"按同一维对齐汇总"(例如按地区同时看订单金额与供应成本),这是润乾 NLQ 里专门的查询范式之一。
四、环境准备
项 |
说明 |
润乾报表 |
运行实例 D:/soft/raqsoft( Tomcat 6868 + HSQLDB 9001 ) |
授权文件 |
润乾报表开发授权.xml |
数据库 |
HSQLDB , jdbc:hsqldb:hsql://127.0.0.1/tpch ,用户 sa 空密码, db.type=13 |
驱动 jar |
D:/soft/common/jdbc/hsqldb-2.7.3-jdk8.jar |
产品 lib |
…/demo/WEB-INF/lib/ ( datalogic.jar 、 nlq.jar 等) |
五、实施步骤(6 步)
下面是完整实施路径,每一步的产出都真实落盘。为了让读者能复现,我把每一步的具体要求做成了提示词(见第九节),WorkBuddy 会按序执行。
1. 生成元数据 wb_tpch.lmd:连库读 8 张表真实结构,生成 DQL 元数据 JSON(顶层 {tableList, namedDimList})。物理表 type:0,含主键与 fkList;日期型字段建"日期"虚拟维,字符串枚举字段(订单状态、订单优先级、市场细分、制造商、品牌、零件类型、包装、退货标记、明细状态、运输说明、运输方式)建枚举虚拟维。
2. 生成汉语词典 wb_tpch.nlq:基于 lmd 生成 NLQ 词典,含全局词表 + dimConfigList + tableViewList(8 张表的字段视图、字段簇、实体)。这是最考验细节的一步,详见第六节。
3. 配置数据源(两个 xml):raqsoftConfig.xml 注册 DQL4wbTPCH(LogicDriver + lmd 路径)和 NLQ4wbTPCH(NLQDriver + nlq 名)两个 DB;nlqConfig.xml 增加 <NLQ name="wb_tpch"> 绑定。
4. 生成 LLM 提示词 wb_tpch_nlq_nspl.md:把词典里的表、字段、维词、常数词、聚合词,以及"自然语言→规范文本"的翻译规则写成提示词,喂给任意大模型。
5. Web 上线 wb_tpch.jsp:复制自带 nlq.jsp,改 dataSource="NLQ4wbTPCH" 和标题,并修复 <dd_qq> 占位符还原问题。
6. 真连真查验证:写 JDBC 程序,分别用 DQL 驱动验证多表深路径聚合、用 NLQ 驱动验证汉语查询,打印真实结果。
六、深度解析:多表关联在词典里到底怎么写
相较于单表 Demo 抄一遍就能跑,多表关联则必须把下面几件事做对,任何一件错了都是"查不出"或"查错"。
(1)元数据里的外键关系(fkList)是穿越的基石
.lmd 里每个物理表要声明外键,例如订单表 ORDERS 声明 fkList 指向 CUSTOMER(通过 O_CUSTKEY),客户表 CUSTOMER 再声明指向 NATION(通过 C_NATIONKEY),国家表 NATION 声明指向 REGION(通过 N_REGIONKEY)。引擎就是靠这一串 fkList 把"地区"这个顶层维度沿外键链自动连回订单表的。
(2)词典字段视图的 expStr 必须终止于 "外键引用字段(FK key)"
这是最容易踩的坑。.nlq 的 tableViewList 里,每个字段视图用 expStr 表达取值路径,规则是 表名. 字段名 或 fkN. 字段名. 字段名…。路径必须终止于外键引用字段本身,而不是被引用表的名称字段。举两个实例:
· 国家名称:写 fk1.N_NATIONKEY(终止于外键 key),不能写成 fk1.N_NAME;
· 客户地区:写 fk1.C_NATIONKEY.N_REGIONKEY,一路穿越到地区的外键 key。
为什么?因为外键 key 才是引擎能按 fkList 继续往下穿越的锚点,直接写名称字段会把穿越链打断。
(3)dataType 两套编码,别混
.lmd 里字段类型用 java.sql.Types 编码(INTEGER=4、VARCHAR=12、DOUBLE=8、DATE=91);而 .nlq 字段视图的 dataType 用的是另一套(数值=1、字符串=11、日期=8)。两处值不一样,照抄错误会直接导致类型解析失败。
(4)金额字段挂 unitName,字段簇只放字符串维度
给订单金额、订购金额等数值字段加 unitName:"元",这样"订单金额 大于 10万元"这类带单位的过滤才能被识别;fieldCluster 字段簇只放字符串维度字段,数值字段不进簇,否则簇词会错误指向含数值的簇。
(5)dimConfigList.dimName 必须等于 lmd 里的维表名
实体维(REGION/NATION 等)用物理表名,日期/枚举维用虚拟表名(如"日期""订单状态"),词典里的 dimName 要和 .lmd 的 namedDimList 完全对齐,否则"按维分组"会匹配失败。
把这些做对之后,DQL 侧的深路径就能写出下面这种四层穿越查询:
SELECT ORDERS.O_CUSTKEY.C_NATIONKEY.N_REGIONKEY.R_NAME,
ORDERS.sum(O_TOTALPRICE)
FROM ORDERS
BY ORDERS.O_CUSTKEY.C_NATIONKEY.N_REGIONKEY.R_NAME
路径 O_CUSTKEY → C_NATIONKEY → N_REGIONKEY → R_NAME 从订单一路跳到地区,一次聚合就拿到"各地区订单金额"。
七、成果物清单
文件 |
说明 |
位置 |
wb_tpch.lmd |
DQL 元数据( 8 表 + 外键 + 日期 / 枚举虚拟维) |
artifacts/ |
wb_tpch.nlq |
汉语词典(词表 + 字段视图 + 维 / 常数词 + 聚合词) |
artifacts/ |
wb_tpch_nlq_nspl.md |
LLM 提示词(自然语言 → 规范文本) |
artifacts/ |
raqsoftConfig.xml |
DQL/NLQ 数据源配置( DQL4wbTPCH / NLQ4wbTPCH ) |
artifacts/ |
nlqConfig.xml |
NLQ 实例配置( wb_tpch → 数据源 + 元数据) |
artifacts/ |
wb_tpch.jsp |
Web 查询页( dataSource=NLQ4wbTPCH ) |
artifacts/ |
VerifyNLQ.java |
JDBC 真连真查验证程序 |
artifacts/ |
00-总提示词.md |
一条搞定的总提示词(最小代价方案) |
prompts/ |
01-05-分阶段提示词.md |
分阶段提示词(备选) |
prompts/ |
八、验证结果(真连真查,非纸上谈兵)
DQL 侧——多表深路径与日期分层:
查询 |
结果 |
订单全表 count(*) / sum(O_TOTALPRICE) |
12380 单 |
各地区订单金额( 4 层穿越) |
见下方 5 行 |
订单明细按订单日期 "年月" 分层聚合 |
正确分层 |
四层穿越返回的五个地区金额(真实查询结果):
地区 R_NAME |
订单金额总和 |
AFRICA |
3.7866×10⁸ |
EUROPE |
3.8184×10⁸ |
AMERICA |
3.7368×10⁸ |
ASIA |
3.7534×10⁸ |
MIDDLE EAST |
3.6470×10⁸ |
NLQ 侧——汉语查询全部命中并生成 MQL:
自然语言 |
生成的核心 MQL |
各地区的订单金额总和 |
… ON REGION AS 地区 FROM ORDERS … BY 客户地区 |
1996 年每月的订单数 |
… ON 月 FROM ORDERS WHERE 订单日期#年=1996 BY 订单日期#月 |
各品牌的订购金额总和 |
… ON 品牌 FROM LINEITEM … BY 品牌 |
各运输方式的订购金额总和 |
… ON 运输方式 FROM LINEITEM … BY 运输方式 |
注意这几条查询的"层次感":前两条都做了外键穿越("地区"从订单穿越三层、"月"来自订单明细穿越到订单的日期维),后两条则是明细表(LINEITEM)自身的枚举维(品牌、运输方式)。这说明词典既能处理跨表穿越,也能处理单表枚举维,多表关联的三种难点都验证通过了。
九、最小代价路径:一条提示词搞定
如果你已经有一套润乾 NLQ 环境,最省事的方式不是照着本文一步步敲,而是把 00- 总提示词.md 整段复制给 WorkBuddy(Agent 模式)。那段提示词里写好了:
· 完整的环境信息(安装目录、数据库 URL、驱动 jar、授权文件路径、8 张表、实例名 wb_tpch);
· 6 项按序执行的任务(lmd → nlq → 双 xml 配置 → LLM 提示词 → jsp → 真连真查验证);
· 全部关键规则(外键关系、expStr 终止于 FK key、两套 dataType、unitName、字段簇等,见第六节);
· 约束(加载 raqsoft-dql-lmd 经验、中文输出、每步产出文件路径 + 验证结果)。
WorkBuddy 会直接连库读表结构、生成元数据与词典、写配置、编译运行验证程序,把每一步的产出路径和真实验证结果报给你确认。你唯一要做的,是每步结束后简单确认一下。这就是"一天上线、最小代价"的含义——把重复的、容易错的细节固化进提示词,让 Agent 去执行,人只做判断。
十、踩坑与经验总结
1. expStr 必须终止于 FK key:多表穿越最核心的坑,写错成名称字段会静默查不出,报错信息还不明显。务必写成 fk1.N_NATIONKEY 而非 fk1.N_NAME。
2. 两套 dataType 编码:lmd 用 java.sql.Types,nlq 用 1/11/8,别照抄。
3. NLQ 需要 raqsoftConfig.xml:独立跑 NLQDriver 时会报"需要配置raqsoftConfig.xml",要在 nlqConfig.xml 的 <NLQ> 节点里显式指定 <RaqsoftConfig> 指向正确的 raqsoftConfig.xml。
4. jsp 的 <dd_qq> 占位符:从自带 nlq.jsp 复制改造成 wb_tpch.jsp 后,占位符还原需要三处 .replaceAll("<dd_qq>","\""),否则页面显示会带多余引号。
5. 验证要 "真连真查":不要只看配置对不对,要写 JDBC 程序走驱动真正查一遍、打印结果,才算上线。
总结
润乾 NLQ 的两段式设计,让"智能问数"从"大模型直接吐 SQL"的高风险路径,变成了"大模型翻译规范文本 + 引擎受控执行"的可控路径。智能问数的主要实施难点在语义建模——外键穿越、主子表、跨表聚合,这三件事的答案都在 .lmd 的 fkList 和 .nlq 的字段视图路径里。
借助 WorkBuddy 这种能"动手"的 Agent,可以把这一整套繁琐、易错的配置过程,收敛成一段可复用的提示词。人负责定义规则和做判断,Agent 负责连接、生成、验证——这才是"一天上线"背后真正省下的成本。如果你想在现有润乾环境上快速复现,就从 00- 总提示词.md 开始;如果你想理解为什么这样配,请回到第六节逐条对照 .lmd 和 .nlq。
附录:总提示词
# 总提示词(一条搞定,最小代价)
> 把下面整段话复制给 WorkBuddy(Agent 模式)。WorkBuddy 会直接操作本机:连接数据库、读写文件、生成配置、跑通验证。你只需在每步产出后简单确认即可。
---
帮我基于润乾NLQ 上线一个智能问数 DEMO,数据集用 TPCH(HSQLDB),是多表关联场景(比单表复杂,涉及外键穿越、主子表、跨表聚合)。
## 环境信息
- 安装根目录:`D:/soft/raqsoft`(运行实例),文件名/服务名统一。
- 数据库:HSQLDB,`url=jdbc:hsqldb:hsql://127.0.0.1/tpch`,`driver=org.hsqldb.jdbcDriver`,`user=sa`,`password=`(空),`db.type=13`。
- 底层驱动 jar:`D:/soft/common/jdbc/hsqldb-2.7.3-jdk8.jar`。
- 产品 lib:`D:/soft/raqsoft/report/web/webapps/demo/WEB-INF/lib/`(datalogic.jar / nlq.jar 等)。
- 授权文件:`D:\tools\professional\润乾报表研发人员内部专用授权20261231含nlr.xml`。
- 8 张 TPCH 表:REGION、NATION、SUPPLIER、CUSTOMER、PART、PARTSUPP、ORDERS、LINEITEM(DEMO schema)。
- 实例命名:`wb_tpch`(lmd/nlq/jsp 统一用这个名)。
## 任务(按顺序全部完成)
1. **生成元数据 `wb_tpch.lmd`**:连接数据库读 8 张表的真实结构,生成 DQL 元数据(JSON,UTF-8,顶层 `{tableList, namedDimList}`)。物理表 `type:0`,含主键与 `fkList`(外键关系:NATION→REGION、SUPPLIER→NATION、CUSTOMER→NATION、PARTSUPP→PART/SUPPLIER、ORDERS→CUSTOMER、LINEITEM→ORDERS/PART/SUPPLIER)。日期型字段建「日期」虚拟维(`type:2`, dimType 5,levelList 含年/季度/月/日/年月/月日/年季度/星期/周/年周),字符串枚举字段建枚举虚拟维(订单状态、订单优先级、市场细分、制造商、品牌、零件类型、包装、退货标记、明细状态、运输说明、运输方式)。字段 desc 用中文。输出到 `WEB-INF/files/dql/wb_tpch.lmd`。
2. **生成汉语词典 `wb_tpch.nlq`**:基于上面的 lmd,生成 NLQ 词典(JSON)。要点(务必遵守,否则查不出):
- 全局词表(无效词/量纲/存在词/非法词/宏词/聚合词/连词/比较词)参考 demo 自带 `orders.nlq` 直接复用。
- `dimConfigList` 里 `dimName` 必须等于 lmd 中维表名(实体维=物理表名如 REGION/NATION,日期/枚举维=虚拟表名如 日期/订单状态)。
- `tableViewList` 的字段视图 `expStr` 规则:`表名.字段名` 或 `fkN.字段名.字段名…`;**路径必须终止于外键引用字段(FK key)**,例如国家名称=`fk1.N_NATIONKEY`(不是 `fk1.N_NAME`)、客户地区=`fk1.C_NATIONKEY.N_REGIONKEY`。原生主键字段不要加 `isFKCluster:true`。
- 字段簇 `fieldCluster` 只放字符串维度字段,**数值字段不进簇**;簇词不指向含数值的簇。
- 字段视图 `dataType`:数值=1、字符串=11、日期=8(与 lmd 不同)。金额字段加 `unitName:"元"`。
- 每张表配 `entityList` 实体(订单/订单明细/客户/供应商/零件/供应/国家/地区),多表外键字段用中文命名(如「客户国家」「客户地区」)。
- 输出到 `WEB-INF/files/dql/wb_tpch.nlq`。
3. **配置数据源(xml)**:
- `WEB-INF/raqsoftConfig.xml` 的 `DBList` 加两个 DB:
- `DQL4wbTPCH`:`url=jdbc:datalogic://?lmd=<wb_tpch.lmd绝对路径>&db.url=jdbc:hsqldb:hsql://127.0.0.1/tpch&db.driver=org.hsqldb.jdbcDriver&db.user=sa&db.password=&db.type=13`,`driver=com.datalogic.jdbc.LogicDriver`,type=0。
- `NLQ4wbTPCH`:`url=jdbc:datalogic:nlq://?nlq=wb_tpch`,`driver=com.nlq.datalogic.jdbc.NLQDriver`,type=0。
- `WEB-INF/classes/nlqConfig.xml` 加 `<NLQ name="wb_tpch"><DB>DQL4wbTPCH</DB><MetaData><wb_tpch.nlq绝对路径></MetaData><RaqsoftConfig></RaqsoftConfig></NLQ>`。
4. **生成 LLM 提示词 `wb_tpch_nlq_nspl.md`**:基于 nlq 的词典,参考 `WEB-INF/classes/tpch_nlq_nspl.md` 结构,生成「自然语言→规范文本(nlq+nspl)」的提示词,词典部分与 nlq 完全一致(表/字段/维词/常数词/聚合词)。
5. **Web 上线**:复制 `raqsoft/dql/jsp/nlq.jsp` → `raqsoft/dql/jsp/wb_tpch.jsp`,改 `dataSource="NLQ4wbTPCH"` 和 `<title>`;并修复 `<dd_qq>` 占位符还原(三处 `.replaceAll("<dd_qq>","\"")`)。
6. **真连真查验证**:写一个 JDBC 测试程序,用 DQL 驱动验证多表深路径聚合(如「各地区的订单金额总和」→ 订单→客户→国家→地区 4 层穿越),用 NLQ 驱动验证汉语查询(如「各地区的订单金额总和」「1996年每月的订单数」「各品牌的订购金额总和」),返回真实结果,确认全链路可用。
## 约束
- 如果你环境里有 raqsoft-dql-lmd 这个 skill,直接加载并按其规则执行(它记录了 tpch 多表关联的全部坑)。
- 中文输出;报错先查编译/运行命令、classpath、URL 是否写对。
- 每完成一项,报告产出文件路径 + 验证结果,等我确认再继续。
成果物下载:NLQDEMOrar
