沿着关系走
从一个对象走一步,用 pivotTo 一批一批地走,用派生属性把关系数成数字,在数据库里读链接上的属性。

本课目标
读完这一课,你将能够
- 沿一条关系取到相关的对象,分清返回一个还是一批
- 用
pivotTo(searchAround)把一个对象集换成它的相关对象集 - 沿关系数出数字,并读出多对多关系上的属性
从一个对象走一步
关系定义好了,这一节用它。最小的一步是:给一个对象和一个链接端的名字,取回对面的对象。名字就是你在定义里起的那个,从销售订单出发有 lines、customer、workOrders、shipments。
aidc semantic links salesOrder SO-1001 lines
在 SDK 里是同一件事:
const client = semantic.ontology();
const lines = await client.linked("salesOrder", "SO-1001", "lines"); // a page of order lines
const owner = await client.linked("salesOrder", "SO-1001", "customer"); // at most one customer
指向「一」的端最多返回一个对象,指向「多」的端返回一页。在 /semantic 里,Object View 给每条关系列一个列表,点一个就走到那个对象;本体(Ontology)图里,对象类型是节点,关系是边。
对象集:一批一批地走
真实的问题很少从一个对象开始。「战略客户的在产订单,分别排在哪几条产线上?」要从一批对象出发走好几步。做法是对象集:先取一批、过滤,再沿关系走一步(searchAround,SDK 里叫 pivotTo),得到另一个类型的一批对象,可以接着过滤、接着走。
- 01客户过滤:
tier是strategic - 02订单
pivotTo("orders"),再过滤status - 03工单
pivotTo("workOrders") - 04产线
pivotTo("line")
const lines = await client.objects("customer")
.where({ tier: "strategic" })
.pivotTo("orders")
.where({ status: "inProduction" })
.pivotTo("workOrders")
.pivotTo("line")
.fetchPage({ $pageSize: 50 });
这条链有三点要记住。每走一步换一次类型,下一步的名字必须是当前类型上的链接端名。结果是集合,同一条产线只算一次。整条链编译成一条 SQL,不是在循环里一个个查,所以放心写长链。
顺序很重要。在 orders 之后过滤 status,问的是「在产的订单」;先走到 workOrders 再过滤工单的 status,问的是「在跑的工单」,两个答案不是一回事。写长链时,把每一步想成一句话:这一批是什么,再决定下一步。
关系也不绕过权限:走到的每个类型都按你的权限检查,看不见的类型,沿关系也走不过去。
最常见的错是把端名用反:orders 是客户上的名字,在订单集上再写 pivotTo("orders"),找不到这条链接;要回到客户,写的是 customer。找不到名字时,用 aidc semantic ontology 看每个类型有哪些链接端。
同样的意思写成 JSON,就是交给 CLI 的对象集。下面是它的第一步:战略客户的订单。
aidc semantic object-set '{"type":"searchAround","objectSet":{"type":"filter","objectSet":{"type":"base","objectType":"customer"},"where":{"type":"eq","field":"tier","value":"strategic"}},"link":"orders"}'
把关系数成数字
关系也能拿来数。派生属性(Derived Property)在读的时候沿关系计算:数个数、求和、取最大最小、收集一个列表。三条规则:最多走 3 跳;只读,不能过滤、不能排序;路上只要有一头是「多」,就必须用聚合。收集列表时,limit 决定最多收几个,缺省 10 个,最多 100 个。
{
"name": "inspectionCount",
"type": "integer",
"title": "Inspections",
"derived": {
"linkPath": ["orders", "workOrders", "inspections"],
"aggregation": { "type": "count" }
}
}
inspectionCount 走了三跳:客户、订单、工单、检验。它永远和实际一致,不用人维护;代价是每次读都要算。一个经验数字:一次查询涉及一万个对象以内,放心用;再大就先测延迟。
想按条件数,比如「检验失败的有几个」,派生属性做不到,因为它不能过滤。改成先过滤对象集,再聚合。withProperties 则是在一次查询里临时加一个派生属性,不用改定义。
const failed = await client.objects("inspection")
.where({ result: "fail" })
.aggregate({ $select: { $count: "unordered" } });
const busy = await client.objects("productionLine")
.withProperties({ workOrderCount: (b) => b.pivotTo("workOrders").aggregate("$count") })
.fetchPage();
多对多:读链接上的属性
pivotTo("materials") 取回的是物料对象,里面没有 quantityPerUnit:它属于链接,不属于任何一个对象。链接上的属性在 Semantic 数据库里读。Postgres 方言只为符合条件的多对多关系生成同名视图。两端的 Object Type 都要有视图,关系名不能超过 63 字节。视图两端各一列,列名是「对象类型_链接端名」,例如 product_materials,值是沿这一端走到的对象的主键,其余的列就是链接上的属性。
-- materials needed for one unit of product EB-100, with quantities
SELECT m."name", b."quantityPerUnit"
FROM "productMaterials" b
JOIN "material" m ON m."materialCode" = b."product_materials"
WHERE b."material_products" = 'EB-100';
读的规则和别处一样:一条 SELECT,最多 10,000 行、20 秒,只读。要改这条 BOM,仍然走 Action。
下一节换一种「连接」:不是对象连对象,而是把形状相同的类型放在一起用。
要点
- 从一个对象走一步:给出对象和链接端的名字,指向「一」返回一个,指向「多」返回一页。
- 对象集用
pivotTo(searchAround)一批一批地换类型,整条链编译成一条 SQL。 - 派生属性沿关系读时计算,最多 3 跳,不能过滤、不能排序;要按条件数,先过滤对象集再聚合。
- 链接自己的属性不在对象上,到 Semantic 数据库的关系视图里读。
练一练
在示例工厂里走一走
有 Semantic 数据的话在真数据上做;没有就先把语句写出来。
用 aidc semantic links 或 SDK 的 linked,从一张销售订单走到它的订单行和客户。哪个端返回一个对象,哪个返回一页?
写出「有在途采购订单的物料,是哪些供应商在供」:从 material 出发,经 purchaseOrders 到 supplier。
仿上面的 SQL,写出「用到某种物料的所有产品,以及各自的用量」。换一头查以后,两个列名怎么换?
小测
选一个答案,马上看解析。
Q1client.objects("customer").pivotTo("orders") 得到的是什么?
pivotTo 换了类型:结果是另一个类型的对象集。要数个数,再接一步聚合。
Q2想在客户列表里按「在产订单数」排序,能用派生属性吗?
派生属性是读时算的:三跳的限制说的是路径长度,和能不能过滤、排序无关。
Q3quantityPerUnit 想查出来,去哪里读?
它属于链接,不属于任何一个对象;多对多关系的视图把链接属性当列给出来。