副标题:从业务指标、查询条件到数据库 SQL,逆向回放一个结果
前文:客户说“销售额”,系统怎样落到订单含税金额或开票含税销售额?
上一篇解决的是“销售额到底指哪个事实”。但用户继续追问时,问题会变成另一种。
业务用户通常不会这样说:
请解释刚才的开票销售额是如何生成的,列出使用的 QM、TM、过滤条件和 SQL。
他们更可能这样问:
这个数是怎么算出来的?
为什么是这个金额?
能不能把这笔销售额的组成拆开给我看看?
这里算的是订单金额,还是已经开票的金额?
我只想看东南区今年开票的部分,刚才有没有算进去?
这些问法表面上不同,背后都是在追问同一件事:刚才的结果经过了哪些业务口径、筛选条件和数据来源。
普通问数只要返回一个结果,解释型问数还要回答结果的生成路径。企业不一定一开始就要建设完整的数据血缘平台,但至少应该能把一次查询回放到它实际使用的语义模型、查询 DSL、权限条件、物理来源和生成 SQL。
这次补上的能力就是按需解释查询:用户用业务语言追问结果来源时,Agent 才调用解释工具;普通查询仍然走原来的执行入口。业务用户不需要知道 QM、TM 或 SQL,系统在内部把“这个数怎么算出来的”转换成解释请求。
当前 Foggy 已发布:
foggy-runtime query explain,CLI 版本为 v0.1.22。dataset.explain_query,Launcher 版本为 foggy-runtime-launcher-v0.1.17。DEFINITION:解释模型和字段定义。RECOMPILED:按当前请求、当前模型版本和当前权限上下文重新编译,并返回查询证据。业务用户可以这样追问:
这个月的开票销售额为什么是这个数?能不能说说它是怎么算出来的?
Agent 不需要每次都把底层过程塞进普通回答,而是在出现“为什么”“怎么来的”“哪些单据组成”“有没有算进某个区域”这类追问时调用 dataset.explain_query。返回结果再由 Agent 翻译成业务人员能读懂的说明。
如果是开发人员排查问题,才可能直接要求“列出 QM、TM、过滤条件和 SQL”。这属于内部调试表达,不应该要求客户用技术术语提问。
在 WWI 的发票明细模型中,用户看到的是一个销售事实,技术上它有稳定的唯一名称:
QM: wwi_oltp_invoice_lines
measure: totalIncludingTax
caption: 含税销售额
aggregation: SUM
QM 负责把用户可用的查询字段组织起来。它说明这个问题可以按哪些维度筛选、按哪些字段汇总,以及“含税销售额”这个度量允许怎样聚合。
这里的关键不是把 QM 当成数据库表的别名,而是让 Agent 能回答:这次结果选择的是发票事实,还是订单事实;选择的是含税金额,还是不含税金额;日期过滤落在发票日期,还是订单日期。
QM 继续向下引用 TM。当前链路可以简化为:
用户问题
→ QM: wwi_oltp_invoice_lines
→ TM: WwiOltpInvoiceLineModel
→ measure: totalIncludingTax
→ SUM(total_including_tax)
→ Sales.InvoiceLines.ExtendedPrice
TM 负责描述表级事实、字段、维度、度量和必要的物理映射。WWI 这条链中,TM 的 viewSql 将发票明细里的 ExtendedPrice 映射为 total_including_tax,并声明物理来源是 Sales.InvoiceLines 的 ExtendedPrice。
因此,解释结果时不能只说“查了发票表”。至少要说清楚:使用了哪个事实模型、哪个度量、经过了什么字段映射,以及最终的聚合方式是什么。
模型定义只能说明“能查什么”,不能代替一次查询的具体条件。一次实际的 DSL 还会记录本次问题中的列、时间范围、分组和排序,例如:
{
"columns": [
{"field": "customer", "agg": "GROUP"},
{"field": "totalIncludingTax", "agg": "SUM"}
],
"slices": [
{"field": "invoiceDate", "op": "BETWEEN", "values": ["2016-01-01", "2016-06-30"]}
]
}
解释器会把这部分整理成可读信息:按客户分组,统计 2016 年上半年的发票含税销售额,并按用户要求排序。这样用户看到的不是一条脱离上下文的 SQL,而是从业务问题到结构化查询的中间证据。
查询解释不能只展示用户主动输入的筛选条件。模型权限可能自动加入条件,而且必须和用户的筛选区分开。
例如,在一个带有区域权限的查询中:
用户条件:invoiceDate 在 2016-01-01 到 2016-06-30
模型权限:customer.salesTerritory = Southeast
前者来自 USER_SLICE,后者来自 MODEL_PERMISSION。两者都影响最终结果,但来源不同。解释结果应明确告诉用户,哪些条件是他提出的,哪些条件是系统根据可信身份注入的;不能把权限条件伪装成用户筛选,也不能让模型根据自然语言猜测用户身份。
如果模型配置了预聚合,解释结果还需要说明本次重新编译选择了原始表、预聚合直读、预聚合汇总还是混合路径。这属于查询规划证据,不等于把所有底层物理细节都暴露给业务用户,但对开发和审计很有价值。
这是当前能力最需要说清楚的边界。
RECOMPILED 会在当前模型版本、当前请求和当前授权上下文下重新编译查询。它可以返回:
但它不是历史查询的真实执行录像。当前版本暂不支持真实的 EXECUTED_TRACE,执行轨迹会标记为未评估。也就是说,如果模型定义、权限配置或数据已经变化,重新编译得到的是“现在按同样请求会怎样生成”,不是“当时数据库实际执行的每一个细节”。
这两个概念不能混用:
RECOMPILED 当前状态下重新推导查询路径
EXECUTED_TRACE 历史请求实际执行过的路径和运行证据
前者足以帮助定位指标映射、DSL 条件、权限注入和 SQL 生成问题;后者还需要查询日志、执行 ID、模型版本、运行时参数和结果快照等持久化能力。
普通业务用户问“上个月哪个客户销售额最高”,首先需要的是答案和可继续筛选的结果。每次都返回 QM、TM、SQL,会增加阅读成本,也会把内部实现细节暴露在不必要的场景里。
但当用户追问“为什么是这个数”时,系统必须有一条受控的证据路径。按需解释的边界比较清楚:
普通问题 → query execute → 返回业务结果
解释性追问 → dataset.explain_query → 返回生成路径和解释证据
它不会修改现有 query execute、models describe 等工具协议,也不把解释工具变成一个可以任意执行 SQL 的入口。解释工具的价值,是把已经允许的语义查询讲清楚,而不是绕过模型、权限和查询引擎。
当前这篇文章解决的是“AI 给出的结果如何解释”。下一步更值得验证的是:业务系统、BI 和 AI 在不强制共用同一个 QM 的情况下,怎样共享业务事实和口径。
理想的用户路径应该是:
AI 问数得到客户销售额
→ 打开业务系统里的可筛选表格
→ 继续增加筛选、分页和排序
→ 点击客户或单据跳回业务系统详情
→ BI 使用对齐后的事实和指标做趋势分析
这里的重点不是让三个入口长得一样,也不是强迫它们使用同一个 QM,而是让它们共享可解释的事实、指标、权限和刷新边界。下一篇将从这个交付闭环开始,讨论哪些内容可以复用,哪些仍然需要业务系统、BI 或数据实施分别处理。