本篇解决:同一句"华东的活跃客户 GMV",写进 RDF 三元组、OWL 公理、属性图、语义层 YAML,各自买到什么、赔上什么。
前置:第 1 篇(三种"本体"的划界)。
读完得到:一张"表达力 × 落地成本"的定位图,和一条判据——能被兑现的声明才值得写下来。
一、同一句问话,四种载体的四种写法
“华东的活跃客户 GMV,按月看。”
这句话里藏着四个必须回答的问题:华东是哪些值、活跃是什么条件、GMV在哪个对象的哪个字段上聚合、从订单走到区域要经过谁。知识表示载体(knowledge representation carrier)的差别,不在"能不能把答案写下来",而在写下来之后由谁保证它为真。
先给四种写法各一段(结构示意,非任何项目原文):
# ① RDF 三元组:只回答"这句话引用了哪些概念" ex:q_gmv_by_month ex:metric ex:gmv . ex:q_gmv_by_month ex:filterRegion ex:华东 . ex:华东 ex:activeRule ex:RecentPayer . # "活跃"挂在一个可复用定义上 # ② RDFS + OWL 公理:回答"什么才算数" ex:ActiveCustomer owl:equivalentClass [ a owl:Restriction ; owl:onProperty ex:placedOrder ; owl:minCardinality "1"^^xsd:nonNegativeInteger ] . ex:basedIn a owl:TransitiveProperty . # 上海→华东、华东→全国,于是可推出 上海→全国 # ③ 属性图:回答"点和边各自带什么" (:Region {name:'华东'}) -[:CONTAINS]-> (:City {name:'上海'}) (:Customer {id:1001}) -[:PLACED {at:'2026-03-11'}]-> (:Order {pay:230}) # ④ 语义层 YAML(本仓库参考实现,字段名取自真实文件) # links.order_items: cardinality "1:N" / fan_out true / status confirmed # terms.华东: expands_to 7 个省市名 / verified_against dw.dim_region.area_name # metrics.gmv: agg sum(pay_amount) / filters order_status='paid' / additivity full四段描述的是同一件事,兑现方却完全不同:
- ①最好写,也最容易骗人。谓语词表是自定义的,写
ex:filterRegion还是ex:region没有任何机制阻止你——RDF 给的是可互操作的结构,不是约束。 - ②一旦声明
TransitiveProperty,“上海的订单"就会被推理机算进"华东”。买到了自动展开的层级,赔上的是:这条推出来的关系不在任何一张表里,只有走推理机的查询路径看得见它。 - ③边可以带属性(
PLACED {at:...}),这在 RDF 里要额外造节点才能表达;赔上的是各家图查询方言互不通用。 - ④字段最少,但每一行都直接对应编译决策:
cardinality决定编译器敢不敢走这条边,expands_to决定 WHERE 里放哪些值,additivity决定这个指标能不能再被聚合。
一句话概括本篇的判断标准:表达力不是目的,"可被兑现"才是。兑现方无非三种——推理机、校验器、编译器。
图 2-1|读这张图看什么:越往右,约束越能被机器校验;越往上,建模门槛 + 引擎 + 维护三项成本合计越高。三点观察:右上角 OWL/描述逻辑是"完备但重";左上角那个不起眼的点是人工维护的指标字典与口径文档——表达力接近零,但因为每条口径都要靠人记住并手写进 SQL,维护成本反而很高,这是"浅而贵"象限最常见的真实居民,也是很多团队做语义治理的实际起点;红点是本系列的落点。坐标是定性归纳,不是量化比较(源:12 §一、02 §6)。
红点的依据不是口味:12的路线打分里,方案 D(RDF/OWL + OBDA/Ontop,SPARQL→SQL)加权5.8 分,其中"落地成本"一项只有2 分,是五条路线里最贵的;而"挡高量级错误"这项它拿到 8 分——说明它不是不行,是贵在对的地方之外(源:12 §一)。
第 1 篇划出的三派,在这张图上也各有其位:W3C 形式本体派在右上,Palantir 式操作型本体派落在右下一带(它把权限与动作挂在对象上,表达力不低但落地靠平台而非逻辑),工程语义层派就在红点附近。这个映射是本篇的读图辅助,不是对厂商产品的实测结论(三派能力描述未做产品验证,见第 1 篇与第 13 篇的同类标注)。
二、逐级加钱:从三元组到描述逻辑推理机
表 1|四级台阶,每级只问"多出来什么、要付什么"
| 级别 | 多出来的表达能力 | 立刻要付的代价 | 对 NL 问数的实际用处 |
|---|---|---|---|
| RDF 三元组 | 统一的数据结构、词表可混用 | 无类型无约束,全靠查询者自觉 | 适合当"元数据存储层" |
| RDFS 词汇 | 类层级、属性的 domain/range | 推理负担极小,但仍无"必须" | 对象分类、别名归置 |
| OWL 公理 | 基数、传递性、函数性、否定 | 开放世界语义 + 要养推理机 | 只在真需要公理推理时 |
| 描述逻辑推理机 | 隐含事实、一致性检查 | 真相源分裂、建模门槛陡增 | 与 SQL 口径容易打架 |
RDF 三元组:一张全局三列表。主语、谓语都是 IRI(以字符串标识的全球唯一名字),宾语可以是 IRI 或字面量;字面量的类型是显式写的——Ontop 的映射模板里就出现{wr_name}^^xsd:string这种写法(源:01a §6.1)。对数据工程师,它的心智模型是:一张三列的关系表,列值全是 URL 形状的字符串或带类型的标量。买到的是任何引擎都能读、可增量追加、多个词表能在同一张图里混用;赔上的是零约束——region_id和region_name在图里长得一模一样,类型系统帮不了你。
再包一层就是命名图(named graph):给一组三元组一个带 IRI 的容器。工程直觉是"给图加一个 namespace 维度",常用来把"仓库自述"和"人工确认"两套事实隔开——OrionBelt 的store_ontology_in_rdf/generate_ontology暴露graph_uri入参,正是这个用途(源:01a §4.2)。
RDFS:三条词汇,几乎白送。rdfs:subClassOf给类加层级,rdfs:domain/rdfs:range规定属性该挂在什么类上、指向什么类(源:01a §4.1 的真实片段就在用这两条)。买到遍历方向约束,推理负担几乎为零。但 RDFS 说的是"这个属性一般挂在什么类上",不是"必须挂"——所以"每个订单必须有客户"这条规则在 RDFS 里写不出来。
值得一提的是 OrionBelt 的一个反直觉设计:它的列自带oba:tableName + oba:sqlReference(Self-contained columns),让 SPARQL 只遍历列三元组就能拼出 SELECT,不必绕rdfs:domain一跳(源:01a §4.1)。请记住这条工程判断:形式上更"正"的建模,常常不如让编译器少一次遍历。语义层的字段设计处处是这种取舍。
OWL 公理:把"必须"写进逻辑。工程师先记四件套就够用:
owl:ObjectProperty/owl:FunctionalProperty:函数性 = 一个主语在该属性上最多一个值(“一个订单只指向一个客户”),源:01a §4.1 的真实用法;owl:inverseOf:反向可推,不必为"客户有哪些订单"再建一条边;owl:Restriction+ 基数约束:把"活跃客户"定义成一个类等价式,而不是一个查询;owl:TransitiveProperty:区域层级一行声明完,SQL 里得写递归 CTE。
买到的东西实在,赔上的同样实在,三条:
- 开放世界假设(Open World Assumption, OWA)与数仓语义相反。OWL 认为"没记下来"不等于"没有",只是未知;数据库的
NOT NULL/CHECK/ 外键是封闭世界(Closed World Assumption, CWA)语义——不在枚举里就是违规。你想用 OWL 表达"订单必须有客户",它只会推断"可能有客户"。这是形式本体在企业数仓最常被误用的一点。 - 真相源分裂。推理机推出的事实不落表,BI 报表、下游 ETL、手写 SQL 都看不见它。同一个业务问题,走 SPARQL 和走 SQL 会得到不同答案——口径一致性的前提被打破。
- 门槛与运维。建模要懂描述逻辑,运行要养推理机(HermiT / Pellet / RDFox 一类,源:04 轨道 B)。
描述逻辑(Description Logic, DL)与推理机在做什么。它把本体切成 T-Box(术语层:类与公理)和 A-Box(断言层:实例事实),做三类任务:实例归类(这个客户算不算 ActiveCustomer)、一致性检查(公理有没有自相矛盾)、隐含事实推断。DL 家族的本质是"表达能力"与"可判定性(decidability:保证有限步内给出是/否)"之间的交换,OWL 2 因此切了若干剖面;其中OWL 2 QL是刻意削弱到"查询重写能落进 SQL"的那一档,这正是下一节 OBDA 路线的逻辑地基(源:04 轨道 B;各剖面的具体权衡本轮未逐条核实)。
一段能直接读的 Turtle,逐块拆。Turtle 是 RDF 的简洁文本语法(W3C 标准)。下面这段是 OBA-Core 词表里"订单→客户"这条关系的真实写法:
# 源:本体建模调研/01a §4.1(OrionBelt OBA-Core 0.1,oba.ttl 词表) # 前两行 @prefix 与 oba:/rdfs: 声明为可读性补写(示意);ns: 取该项目 base_uri 默认值 @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix oba: <http://example.com/ontology/oba#> . @prefix ns: <http://example.com/ontology/> . ns:orders_has_customers a owl:ObjectProperty, owl:FunctionalProperty ; oba:foreignKeyColumn "customer_id" ; oba:referencedTable "customers" ; oba:referencedColumn "id" ; oba:sqlJoinCondition "orders.customer_id = customers.id" ; oba:relationshipType "many_to_one" ; oba:semanticName "Placed by Customer" ; oba:businessRule "Every order must reference a valid customer." ; rdfs:domain ns:orders ; rdfs:range ns:customers ; owl:inverseOf ns:customers_referenced_by_orders . # Shared traversable join edge (many-to-one / finer->coarser grain). ns:orders oba:joinsTo ns:customers .图 2-2|读这张图看什么:一个合格的"语义层字段"长在哪几个位置上。注意
oba:sqlJoinCondition与oba:relationshipType是注解属性(owl:AnnotationProperty),它们不参与逻辑推理,却是编译器唯一真正需要的两行——OBA 只定义注解属性,不引入新类(源:01a §4.1)。
⚠️ 未核实:上面这段词表来自 OrionBelt(
ralforion/orionbelt-analytics,47★,2026-09-22 快照),许可证是BUSL-1.1——条款原文写明"可用于任何目的,包括生产使用,但不得把 OrionBelt 嵌入商业产品或作为商业服务的一部分提供,除非购买商业授权",Change Date 2030-03-16 后转 Apache-2.0(源:01a §4.3)。本次未做法务评估。结论:内部自建可参考,结构与思路可以抄,词表文件与代码不能直接进对外产品。它与同篇的 Apache-2.0 项目(Ontop、KAG、sqlsure)不是一回事,选型表里必须单列一栏。
顺带一条贯穿全系列的纪律:这段 TTL 里oba:semanticName和oba:businessRule是给人和 LLM 读的散文,oba:relationshipType才是给机器判的契约。OntoQuery 把这条写成注释级的原则——“prose 给人看,harness 要强制的东西一律声明在 contract”(源:01a §2.5)。同一份文件里,可校验字段与说明文字必须分层放,否则模型会把散文当约束、把约束当装饰。
同一件事在物理层长这样(示意:标准 DDL,非任何项目原文):
ALTERTABLEordersADDCONSTRAINTfk_orders_customersFOREIGNKEY(customer_id)REFERENCEScustomers(id);外键只声明"能连",不声明"连完会不会重复计数",也不声明"从哪一侧聚合是安全的"。TTL 里那两行many_to_one和joinsTo ns:customers(注释写明 finer→coarser)补的正是这个方向信息——它是第 4 篇扇出拦截唯一的机器可读依据。另外提醒一句:企业数仓里物理外键经常根本不建(关系活在 ETL 和文档里),这时"语义边"就是唯一那份可评审记录。这是判断,不是实测。
三、两条"不做推理"的工程路线
路线一 · SHACL:只问形状对不对
SHACL(Shapes Constraint Language,W3C 的形状约束语言)的做法是:给图里的节点定义"形状"——必须有哪些谓词、值是什么类型、是否落在枚举内、基数上下限——然后对数据跑一遍,输出违反清单。
OrionBelt 的真实取舍很能说明问题:域值约束用 SHACL 而不是 OWL restriction,词表oba.ttl与形状oba-shacl.ttl分家,校验命令直接写在样例头部(源:01a §4.1):
pyshacl -s oba-shacl.ttl -e oba.ttl oba-example.ttl为什么工程上常常"只要形状校验、不要推理":① 封闭世界,缺就是错,正好对上数仓的NOT NULL/CHECK直觉;② 产出是可枚举的违规清单,能进 CI 当门禁;③ 不需要推理机,成本低一个量级。类比记法:SHACL 之于 RDF ≈ 本仓库校验器(规则 E001–E012)之于 YAML 本体 ≈ 数仓约束之于表(源:00 §四 全局事实登记表,级别为实测)。
⚠️ 未核实:
oba-shacl.ttl的正文本轮未读(源:01a §12)。本篇只核实到"它存在、被当作约束面、上面那条命令行",具体约束项清单未逐条核对。
路线二 · OBDA:本体在上、SQL 在下
OBDA(Ontology-Based Data Access,基于本体的数据访问)不搬数据、不建 RDF 库:把关系库当虚拟的 RDF 图,SPARQL 进、原生 SQL 出,中间是一层映射。Ontop(Apache-2.0,953★,默认分支version5,源:01a §6)是这条路线的正典,核心是 R2RML 映射 + OWL 2 QL 查询重写(源:04 轨道 B),CLI 里还内置了obda-to-r2rml | r2rml-to-obda双向转换(源:01a §6)。
映射文件.obda是 target/source 两段式(源:01a §6.1):
[MappingDeclaration] @collection [[ mappingId cl_Authors target :author/{wr_code}/ a :Author ; :name {wr_name}^^xsd:string . source select "wr_code", "wr_name" from "tb_affiliated_writers" mappingId cl_AudioBook target :book/{bk_code}/ a :AudioBook . source select "bk_code", "bk_title" from "tb_books" where "bk_type"='A' ]]三处要读懂:{wr_code}是 IRI 模板,本体层的"主键"是拼出来的字符串;^^xsd:string说明类型在 RDF 世界是显式携带的;cl_AudioBook用一条WHERE bk_type='A'表达了"有声书是书的子类"——子类的物理定义归映射层,不归 prompt。ontology2sql 里对应的机制是EntityFragment.role="alternative",两家各造了一遍(源:01a §6.1)。
改写顺序在QuestQueryProcessor.reformulateIntoNativeQuery,反直觉之处是先做 TBox 重写、后做映射展开(unfolding)(源:01a §6.2):
图 2-3|读这张图看什么:所有阶段都在同一棵与 SQL 无关的 IQ 树上跑,最后一步才
NativeQueryGenerator。另两处工程细节值得抄:ontop.avoidPostProcessing这个布尔开关配合NotFullyTranslatableToNativeQueryException,把"能完全下推"和"需要结果层后处理"显式区分开,不可下推成为可诊断错误而不是悄悄降级;query-logging 把"命中了哪些类/属性、扫了哪些表"做成可配置观测面(源:01a §6.3、§6.4)。
这条经典路线对今天的意义,要说准分寸:它证明了"本体层与 SQL 层之间可以放一台确定性机器",这一点被第 8 篇的五层架构直接继承;但它把"自然语言 → SPARQL"那一跳整个留给人手写。顺带两个细节:Ontop 的 CLI 除了query还有materialize(把整库真导成 RDF 文件,支持 gzip/zip 与流式FETCH_SIZE=-1),也就是"虚拟图"与"实体化"两种形态它都留着口子;validate与extractDBMetadata则说明映射本身要被当作代码来检查(源:01a §6)。这两点对本系列的启发是:本体文件要有"校验入口",不能只有"使用入口"。
⚠️ 未核实:截至 2026-09-22 的检索结果,“把 LLM 接在 SPARQL 前面做成产品"的公开实现其实很少——
09 §四判定够格进正文的只有两篇:SPIRES / OntoGPT(arXiv 2304.02711,递归结构化提示抽取本体)与 DeepOnto(arXiv 2307.03067,用 PLM 做本体匹配),它们做的是"用 LLM 建本体”,不是"用 LLM 查询本体";Clio/Ontop×LLM、GraphRAG/KAG 的结构化接地结论本轮未拿到一手论文。不要把它当成熟路线引用。同节另一条判断要记住:本体降低 Text2SQL 错误率的学术证据,目前主要来自语义层 / IR 路线(arXiv 2606.31041),而不是形式本体(OWL/RDF)路线(源:09 §四)。
表 2|三条路线的分工
| 维度 | OWL/DL 推理 | SHACL 形状校验 | OBDA 查询改写 |
|---|---|---|---|
| 世界假设 | 开放世界(缺 ≠ 错) | 封闭世界(缺即违规) | 取决于 TBox 剖面 |
| 主要产物 | 隐含事实、一致性结论 | 违反清单(可进 CI) | 一条原生 SQL |
| 失败形态 | 推出表里没有的事实 | 漏掉未声明的形状 | 下推不了就报错 |
| 建模者要会 | 描述逻辑 | 形状与枚举 | SQL + 映射模板 |
| 本篇态度 | 只在需要公理推理时上 | 强烈推荐的最小成本件 | 编译层的正典参照 |
四、属性图与逻辑形式:KAG 能借什么、不能借什么
属性图(property graph)对工程师有吸引力的地方很朴素:边有类型,点和边都能带属性,查询写法接近直觉。RDF 要在"关系上挂属性",得额外造一个节点。
KAG / OpenSPG(Apache-2.0,9065★,2026-09-22 快照,源:01a §5)走的是"schema-concept 层 DSL + 逻辑形式(Logical Form, LF)引导检索与推理"。两件事值得抄:
一,把派生指标写进本体,而不是写进 prompt。
Company(企业): EntityType properties: totalTransInAmt(总共流入金额): Float rule: [[ Define (s:Company)-[p:totalTransInAmt]->(o:Float) { STRUCTURE { (inCompany:Company)-[fundIn:fundTrans]->(s) } CONSTRAINT { otherTransSum("总共转入金额") = group(s).sum(fundIn.transAmt); o = otherTransSum } } ]]STRUCTURE 说"从哪些边来",CONSTRAINT 说"怎么算"——这与本仓库metrics.yaml里formula+filters的分工是同一件事(源:01a §5)。它的三类顶层类型EntityType / ConceptType / EventType里,概念归类规则单独放concept.rule,也符合"归类口径与结构定义分家"的直觉。
二,LF 分层法。算子以函数签名声明:Retrieval(s=,p=,o=,p.prop=) / Math(content=[], target=) / Deduce(op=judgement|entailment|...) / Output(alias),多跳就是多次 Retrieval,变量只用 alias 绑定,且每个算子必须有注册 executor(源:01a §5)。这和 ontology2sql 用 15 种表达式形状做受控中间表示(controlled IR)校验是同一立场:模型只选名词,不写语法(源:01a §11 结论 3)。KAG 连解析层的容错都有单测兜着:parse_test.py直接断言SPOEntity.parse_logic_form("s1:政务事项[浙江省-杭州市-西湖区申领]")的三元拆解与中文操作符(“等于”)容忍(源:01a §5)——这是"受控 IR 也要有回归测试"的现成范例,第 9 篇会把这套分层写成可抄的提示骨架、第 14 篇会用到同一种评测思路。
但它不是 text-to-SQL 方案:KAG 是"文档/表格 → SPG 图谱 → 逻辑形式引导检索推理 → 问答",仓库里没有把 LF 编译成 SQL 的后端——全仓 1211 个 blob 里路径含sql的只有两本教材的 markdown(源:01a §5)。
⚠️ 未核实:上述断言只覆盖 KAG 仓库,OpenSPG 引擎侧(Java)本轮未取(源:01a §12)。它的适用面是文档 + 表格混合问答(方案 E,加权 4.3 分,源:12 §一);把它当 text-to-SQL 选型项引用是误判。
工程 DSL 谱系预告(细节全在第 10 篇)。五套建模 DSL 的对照维度就那几条:载体、指标是否一等公民、是否显式表达 join 路径、互操作定位、与 Agent 的接口、成熟度风险(源:02 §6)。注意其中四套是 YAML/JSON 文件(Apache Ossie、dbt MetricFlow、Cube、WrenAI MDL),而 SuperSonic没有公开的 YAML 建模格式,建模即写元数据库 + UI/API 配置(源:01a §10)——"载体"这件事本身就决定了一个系统能不能走 ontology as code。而"下限"低得让人安心:sqlsure 只用 5 个概念——grain / additivity / semi_additive_over / sensitive / join cardinality——就挡掉了双计(源:01a §7)。本篇的落点因此可以收成一句话:先写能挡住错误的最小声明集,再决定要不要加厚。
表 3|四种载体 × 兑现方 × 系列归属
| 载体 | 这条问句写到什么程度 | 谁来兑现 | 本系列哪篇展开 |
|---|---|---|---|
| RDF 三元组 | 引用了哪些概念 | 查询者自觉 | 不单独展开 |
| RDFS + OWL 公理 | "活跃"成为可推理的类 | 描述逻辑推理机 | 仅按需(15 篇直答) |
| 属性图 + LF | 边带属性、算子受控 | 注册 executor | 第 12 篇(KAG) |
| 语义层 YAML | 粒度、基数、口径、值域 | 校验器 + 编译器 | 第 3–7 篇逐要素 |
五、本篇依据
证据级别分三类:实测(本仓库可复现)/一手文献(规范、源码、论文)/本调研判断(打分与归纳,非客观定律)。
../本体建模调研/01a-开源项目代码级剖析.md—— §4.1–4.3(oba.ttl 词表片段、SHACL 校验命令、graph_uri、BUSL-1.1 条款原文)、§5(KAG schema DSL、LF 算子签名、“仓库无 SQL 后端”)、§6.1–6.4(.obda target/source、reformulateIntoNativeQuery顺序、avoidPostProcessing、query-logging)、§7(sqlsure 5 概念)、§11–§12(共同骨架、未能核实清单)。级别:一手源码摘要,未做端到端实测。../本体建模调研/02-本体建模方法论与分层框架.md—— §1(本体/语义层/指标层三词划界)、§6(五套 DSL 的对照维度)。级别:一手规范 + 调研归纳。../本体建模调研/09-论文精读与开源模型谱系.md—— §四(本体/OBDA × LLM 一手可核实材料很少)、§2.1(arXiv 2606.31041)。级别:论文,编号已回 arXiv 核对。../本体建模调研/12-选型决策矩阵与路线图.md—— §一(方案 D 加权 5.8 分、落地成本 2 分;方案 E 4.3 分)、§五("该不该上 OWL/RDF"直答)。级别:本调研的判断模型。../本体建模调研/04-学习资料与论文.md—— 轨道 B(RDF 1.1 / OWL 2 / SPARQL / R2RML、OWL 2 QL 与推理机 HermiT/Pellet/RDFox、Protégé)。级别:官方规范与文档导航。../本体建模调研/reference-impl/ontology/joins.yaml、terms.yaml、metrics.yaml—— 第④段示意里引用的字段名。级别:实测(本仓库文件,可直接打开)。./00-系列大纲与写作规范.md§四 全局事实登记表 —— 校验规则条数 E001–E012(原始出处为调研05§5.1)。级别:实测。
星数与许可证是 2026-09-22 的单次快照(源:01a 文件头)。想自己复看或改这三张图:
cd本体学习系列博客&&PYTHONIOENCODING=utf-8 python tools/render_figs.py p02改tools/specs_p02.py即可重画;note字段里写了每张图的数据来源与"示意"标记。
六、小结与下一篇
本篇的四条结论:
- 载体的差别不在语法,在兑现方——推理机、校验器、还是编译器;写下一行声明之前先问"谁会为它报错"。
- OWL 的开放世界语义与数仓的封闭世界约束正好相反,这是形式本体在企业问数里最常被误用的一点;需要公理推理或多系统互操作再上(源:12 §五)。
- 工程上性价比最高的两件是 SHACL 式形状校验和 Ontop 式映射改写:前者把约束变成 CI 能挡的清单,后者把本体层与 SQL 层之间的机器做成确定性的。
- KAG 的逻辑形式分层可以借,SQL 后端不能指望;"本体 × LLM"这条线上一手可核实的材料其实很少,别按成熟路线写方案。
下一篇回到工程本体:《最小可行本体:五类要素,一类都不能少》——把 terms / entities & properties / links / metrics / rules 五要素逐个讲清"缺了会错成什么",并给出本仓库参考实现里对应的真实文件与字段。