语义 SDK:读、写、聚合

第 2 课 · 共 6 课 约 7 分钟

对象集是链式的描述,最后才取数据;聚合的结果有固定形状;Action 先只校验,再执行。

本课目标

读完这一课,你将能够

  • 用对象集写出一个查询:过滤、沿关系走、取一页
  • 写一次聚合,并读对结果的形状:有分组和没分组不一样
  • 用先只校验、再执行的方式调用 Action

对象集:链式写,最后才取

语义 SDK 里,读数据的起点是一个客户端:semantic.ontology()。在应用里它自动带上访客的身份,在 CLI 和智能体里用 Key 所属的公司。从客户端拿到一个对象类型,就得到一个对象集。where、pivotTo、union 都只是在描述集合,什么也不取,直到你调用 fetchPage、asyncIter 或 aggregate。

  1. 01起点objects(type) 给出一个对象集
  2. 02只描述where、pivotTo、union 层层收窄,不发请求
  3. 03才去取fetchPage、asyncIter、aggregate 发出请求
import { semantic } from "/developer/sdk/v1/aidc.js";

const client = semantic.ontology();

const running = client.objects("workOrder").where({ status: "running", plannedQty: { $gte: 40 } });
const page = await running.fetchPage({ $orderBy: { dueAt: "asc" }, $pageSize: 20 });
// page.data[i]:{ __primaryKey, __apiName, __title, status, dueAt, … }

const orders = await running.pivotTo("order").fetchPage();        // 沿关系走:这些工单属于哪些销售订单
const wo = await client.objects("workOrder").fetchOne("WO-1001");  // 按主键取一个

where 里多个属性之间是「且」;要「或」用 $or,取反用 $not,再复杂就用 $and 嵌套,最多三层。比较用 $eq、$ne、$gt、$gte、$lt、$lte、$isNull、$in,数组属性用 $contains,文字匹配用 $startsWith、$containsAnyTerm、$containsAllTerms。

这里的「类型化」有两层意思。一是返回值的形状固定:每个对象都带 __primaryKey、__apiName、__title,再加上属性。二是规则由属性的类型决定:文字匹配(如 $startsWith)只用在字符串上,大小比较用在数字、字符串和日期上,sum、avg 只用在数字上。不合规矩的写法,服务端返回 400 并说明哪里不对。

有一个常见的坑:fetchPage 只取一页,page.data.length 不是总数。要全部读完,用 asyncIter()(for await 一页一页读);要数量,用聚合的 $count。

聚合:先选指标,再选分组

聚合回答「多少、多大」。$select 里每一项要么是 $count,要么是「属性:指标」这样一个平铺的字符串,如 "goodQty:sum";值是排序方向 unordered、asc 或 desc。$groupBy 决定怎么分组:exact 按值、$fixedWidth 等宽分桶、$ranges 自定区间、$duration 按时间。exact 分组的组数缺省上限是 10,000。

const byLine = await client.objects("workOrder")
  .where({ status: "done" })
  .aggregate({
    $select: { $count: "unordered", "goodQty:sum": "desc", "scrapQty:sum": "unordered" },
    $groupBy: { lineCode: "exact" },
  });
// 有分组:[{ $group: { lineCode: "L01" }, $count: 12, goodQty: { sum: 470 }, scrapQty: { sum: 9 } }, …]
// 没有 $groupBy:一个对象 { $count: 12, goodQty: { sum: 470 }, … }

指标能用在哪,看属性的类型:sum、avg 只用于数字,min、max 还能用于日期和时间戳,approximateDistinct、exactDistinct 什么类型都行。读结果有两个坑:带分组的聚合返回数组,没有对象匹配时是空数组,先判断再取 [0];avg、min、max 在没有匹配对象时是 undefined,不要当成 0。

带 $groupBy不带 $groupBy
返回什么数组,每组一行:$group 加各项指标一个对象:$count 和各项指标
没有对象匹配空数组,先判断再取 [0]$count 是 0,avg、min、max 是 undefined
什么时候用要按产线、按月这样分开看只要一个总数,或一个总和

派生属性也可以临时加。withProperties 给对象集加上一个沿关系、读的时候才算出来的值,最多 3 跳,只读。

const lines = await client.objects("productionLine")
  .withProperties({ orderCount: (b) => b.pivotTo("workOrders").aggregate("$count") })
  .fetchPage();

Action:先校验,再执行

改数据只有一个入口,就是 Action。SDK 里 client.action(name) 给出一个 Action,applyAction 接收参数和选项。加上 $validateOnly: true 只校验,不写任何东西,返回 validation.result(VALID 或 INVALID),参数和提交条件逐项列出。确认没问题,再去掉它,换成 $returnEdits: true,看这次改了哪些对象和链接。

const hold = client.action("hold-work-order");

const check = await hold.applyAction({ workOrder: "WO-1001", reason: "来料不良" }, { $validateOnly: true });
// check.validation.result:VALID / INVALID;参数与提交条件逐项列出

const done = await hold.applyAction({ workOrder: "WO-1001", reason: "来料不良" }, { $returnEdits: true });
// done.edits:改了哪些对象,新建或删除了哪些对象和链接

await hold.batchApplyAction([{ workOrder: "WO-1001" }, { workOrder: "WO-1002" }]);   // 一个事务

普通 batchApplyAction 一次最多 20 个请求,放在一个事务里。有一个失败,全部不生效。配置了 writeback webhook 的 Action 一次只能提交一个请求。一次 Action 最多改 10,000 个对象、涉及 50 个 Object Type。

在应用里调用 Action,还要过两道门:应用清单的 semantic.actions 登记了它,访客的角色也在这个 Action 的 roles 里(缺省是 developer、member、editor)。只读访客永远不能写。忘了登记,请求会被拒绝。

别把「只校验」当成「一定能执行」。校验通过只说明此刻可以,执行前数据可能已经变了,最终结果要看执行的返回。

出错时,SDK 把 REST 的错误抛成异常,code 和 message 与信封里的一致;没有凭证不会发匿名请求,直接报未登录。所以 try 一下,按上一课的规则判断是改参数、等一等还是停下来。

要点

  • 对象集是链式的描述:where、pivotTo、union 不取数据,fetchPage、asyncIter、aggregate 才取。
  • 返回值形状固定:__primaryKey、__apiName、__title 加属性;fetchPage 只是一页。
  • 聚合的 $select 是 $count 或「属性:指标」;有分组返回数组,没分组返回一个对象。
  • Action 先 $validateOnly 再执行。普通批量最多 20 个请求;配置 writeback webhook 时一次只允许一个。

练一练

把三个问题写成 SDK 代码

用示例工厂练习:工单、产线、销售订单。写在纸上或编辑器里都行。

写一个对象集:状态为 paused 的工单,沿 order 关系找到它们的销售订单,取前 10 个。

小测

选一个答案,马上看解析。

Q1client.objects("workOrder").where({ status: "running" }) 这一行执行之后,发生了什么?

Q2带 $groupBy: { lineCode: "exact" } 的聚合,没有任何对象匹配时返回什么?

Q3$validateOnly 校验通过之后,接着执行一定会成功吗?

延伸阅读