SQL 与语义搜索

第 6 课 · 共 6 课 约 9 分钟

用只读 SQL 连表、算比率;用向量近邻按意思找相似对象,候选不超过 10,000 个。

本课目标

读完这一课,你将能够

  • 判断一个问题该用对象集、聚合还是 SQL
  • 写出连接一对多和多对多视图的只读 SQL,并用参数传值
  • 用向量近邻按意思找相似的对象,并说出它的上限

什么时候用 SQL

前两节用对象集和聚合提问。对象集读取返回对象,可以接着沿关系走;聚合返回指标和分组结果。Ontology SQL 返回表格的行,能做连表、比率、窗口函数这类对象集做不了的事。两者读的是同一份当前值,人刚改的值马上就看得见。

对象集与聚合SQL
返回什么对象集读取返回对象,能沿关系走、带派生属性;聚合返回指标和分组结果表格的行
擅长沿关系筛选,按接口跨类型取,分页读对象连表、比率、窗口函数、任意组合
上限一页最多 1,000 个对象;一次分组最多 10,000 组一条 SELECT,最多 10,000 行,20 秒
在 Nexus 应用里用 semantic.ontology()应用票据不走 SQL,不能用

选哪个,先问三句:

  1. 要对象,还是要行?

    要拿到对象、接着沿关系走,或者交给应用去展示,用对象集。只要一张表,用 SQL。

  2. 需要连表、比率或窗口函数吗?

    需要就用 SQL。只是按一个属性分组求和,聚合更省事。

  3. 在哪里跑?

    Nexus 应用里用 SDK。分析师、BI 工具、智能体和开发者可以用 SQL。

SQL 只读,写入永远走 Action。

示例工厂里的 SQL

下面使用 Postgres 方言。符合条件的 Object Type 才有视图:部门级、受 Markings 或安全策略保护、名称超过 63 字节、没有存储属性的类型不建视图。表名就是 API name,列名是属性的 API name,含大写字母要加双引号。一对多的关系直接用外键连表。多对多的关系单独一个视图,两端各一列,还带着关系自己的属性:BOM 里每台的用量 quantityPerUnit 就是这样读出来的。接口也有自己的视图,多两列 __objectType 和 __primaryKey,一次就能跨类型查。(表与列的完整规则见实操课的 SQL 一节。)

-- 各产线近 7 天完工工单的报废率:连表拿产线名,再算比率,一条查询出结果
SELECT l.name AS line,
       sum(w."goodQty") AS good, sum(w."scrapQty") AS scrap,
       round(100.0 * sum(w."scrapQty") / nullif(sum(w."goodQty") + sum(w."scrapQty"), 0), 2) AS scrap_pct
FROM "workOrder" w
JOIN "productionLine" l ON l."lineCode" = w."lineCode"
WHERE w.status = 'done' AND w."dueAt" >= now() - interval '7 days'
GROUP BY l.name
ORDER BY scrap_pct DESC;

-- BOM 缺料:productMaterials 是多对多视图,列名是 <对象类型>_<关系>,值是对面对象的主键
SELECT p.name AS product, m.name AS material,
       l."quantityPerUnit", m."onHandQty", m."safetyStockQty"
FROM "productMaterials" l
JOIN product p ON p.sku = l.material_products
JOIN material m ON m."materialCode" = l.product_materials
WHERE m."onHandQty" < m."safetyStockQty"
ORDER BY p.name;

不确定哪一列对着哪一端时,用 aidc semantic database 看表与列。值要放进位置参数 $1、$2,不要拼进 SQL;--explain 只看执行计划,不真正执行。

aidc semantic database
aidc semantic sql --file scrap-by-line.sql --csv
aidc semantic sql 'SELECT status, count(*) FROM "workOrder" WHERE "lineCode" = $1 GROUP BY 1' --param L1
aidc semantic sql --file bom-shortage.sql --explain

Postgres 方言和数据库直连按身份选 reader 或 public,只读,每次查询 20 秒超时。标准 Spark 方言用 aidc semantic sql "…" --dialect spark,按调用者执行行、列和 Markings 权限:不可见对象不返回,不可见属性为 NULL。SQL 里没有派生属性,要用就自己连表再计算。

按意思找:向量与最近邻

关键词搜索找的是字面。「壳体划痕」和「外壳刮伤」说的是同一件事,字面却几乎不重合。向量搜索的做法是:用模型把每段文字变成一个向量(一串数),意思相近的文字,向量也相近;找相似,就是找最近的向量。

在 Semantic 里,向量是一个属性,类型是 vector,定义里写 vectorDimension,最大 4,096。示例工厂给 defect 加一个 descriptionVector,再用 nearestNeighbors 从一个已有缺陷出发,找出最像它的几个:

{ "name": "descriptionVector", "type": "vector", "vectorDimension": 1024, "title": "描述向量" }

查询时,先取出一个缺陷的向量,再找它的近邻:

const seed = await client.objects("defect").fetchOne("D-2031", { $select: ["descriptionVector"] });

const similar = await client.objects("defect")
  .where({ severity: { $in: ["major", "critical"] } })       // 先缩小候选
  .nearestNeighbors(seed.descriptionVector, 5, "descriptionVector")
  .fetchPage();                                               // 按相似度从高到低
  • 相似度用余弦,在内存里算:候选对象(nearestNeighbors 前面的那个对象集)超过 10,000 个就报错,要先用 filter 缩小。
  • 邻居数 numNeighbors 取 1 到 100;查询向量的维数要和属性一致,对不上的对象不会出现在结果里。第一名通常就是你拿来查的那个对象自己。
  • 向量很长。读对象时用 $select 只取要的属性,别把每个对象的向量都拉回来。上线之前,拿两个你已经知道相似的缺陷试一试,看它们是不是互为近邻。

向量怎么来,是你自己的流程:用嵌入模型把文字算成向量,再写进属性。长文档要先切成块,一块一个对象、各带一个向量,再用近邻找最相关的几块。

不过先别急着上向量。要读的文字如果不多、放得进模型的一次输入里,直接给模型读,往往比先检索再读更简单,也更准。向量搜索留给「文字太多、放不下」的时候。

到这里,数据已经进来,也能被问了。下一门课讲动词:怎样用 Action 安全地改这些数据。

要点

  • 对象集读取返回对象,聚合返回指标和分组结果,Ontology SQL 返回行;连表、比率、窗口函数用 SQL。
  • 表名是 Object Type 的 API name;多对多关系是单独的视图,列名是 objectType_relation,还带着关系自己的属性。
  • SQL 只收一条 SELECT,最多 10,000 行、20 秒;值用位置参数传,--explain 只看计划。
  • Nexus 应用里不走 SQL,用 semantic.ontology()。
  • 向量近邻按意思找相似对象:候选不能超过 10,000 个,先用 filter 缩小;文字查询目前还没有。

练一练

用 SQL 与向量再问示例工厂

SQL 用 aidc semantic sql 跑;向量部分先在纸上设计。

先用 aidc semantic database 看 productMaterials 的列,再写出「哪些产品缺料、每台还差多少」的 SQL 并运行。

小测

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

Q1示例工厂的一个 Nexus 应用要读工单数据,能用 SQL 吗?

Q2对 15,000 个缺陷直接做 nearestNeighbors 会怎样?

Q3想直接用一句「壳体有划痕」找相似缺陷(传文字,不传向量),现在能做到吗?

延伸阅读