搜索与聚合

第 5 课 · 共 6 课 约 8 分钟

where 是最多三层的条件树;文字搜索的几种写法;聚合的指标与四种分组,exact 分组最多返回 10,000 组。

本课目标

读完这一课,你将能够

  • 写出带比较、文字、时间和组合条件的 where,并把嵌套控制在三层以内
  • 在几种文字搜索里选对一种
  • 写出带分组的聚合,并说出 maxGroupCount 的上限意味着什么

where:一棵条件树

上一节的对象集里已经出现了 where;这一节把它讲清楚,再讲聚合。where 是一棵条件树:叶子是比较或搜索,枝是 and、or、not。它用在对象集的 filter 里,也用在 Search、Aggregate、Count 这几个接口里。

COMPARE

比较

eq、lt、lte、gt、gte、isNull、in;数组属性用 contains。

TEXT

文字

containsAllTerms、containsAnyTerm、containsAllTermsInOrder、containsAllTermsInOrderPrefixLastTerm、wildcard、regex(startsWith 是旧别名)。

TIME/GEO

时间与位置

relativeDateRange:相对今天的日期范围;withinDistanceOf、withinBoundingBox:地理点在某个范围内。

LOGIC

组合

and、or、not。SDK 里写成 $and、$or、$not;同一层写多个属性,就是「且」。

条件树最多嵌套三层:叶子算一层,每个 and、or、not 各算一层。and 套 or 再套叶子条件,正好三层;在叶子外面再包一层 not,就是四层,会被拒收。

{ "type": "and", "value": [
  { "type": "eq", "field": "status", "value": "running" },
  { "type": "or", "value": [
    { "type": "eq", "field": "lineCode", "value": "L1" },
    { "type": "eq", "field": "lineCode", "value": "L2" } ] } ] }

写到第三层,先想想能不能少一层。这里的 or 只是给同一个属性列了两个值,用 in 就是一个叶子:

client.objects("workOrder").where({ status: "running", lineCode: { $in: ["L1", "L2"] } })

还有一个容易踩的坑:SDK 的 $ne 会展开成 not 加 eq,多占一层。

有两种写法不太显眼。属性为空写成 null,比如找出还没有承诺交期的订单;地理点用 $within,比如找出 50 公里以内的客户(坐标是经度在前、纬度在后):

client.objects("salesOrder").where({ promisedDate: null })
client.objects("customer").where({ location: { $within: { $distance: [50, "km"], $of: [121.47, 31.23] } } })

相对日期用 relativeDateRange:以今天为基准,按你给的时区取整天。下面是「过去 7 天下的订单」。SDK 的 $ 写法里还没有它,要写成 JSON 定义,交给对象集 JSON 或 aidc semantic object-set:

{ "type": "filter",
  "objectSet": { "type": "base", "objectType": "salesOrder" },
  "where": { "type": "relativeDateRange", "field": "orderDate",
             "relativeStartTime": { "value": -7, "timeUnit": "DAYS" }, "timeZoneId": "Asia/Shanghai" } }

在命令行里试 where 最快:

aidc semantic objects workOrder --where '{"status":"running","lineCode":{"$in":["L1","L2"]}}' --order-by dueAt:asc --page-size 20

文字搜索:几种词,几种顺序

文字搜索先把你给的字符串按空白和标点切成词,再去找。几种写法的差别,在「要不要全部命中」和「要不要按顺序」:

  • containsAllTerms:包含所有词,不管顺序;containsAnyTerm:至少包含一个词。
  • containsAllTermsInOrder:所有词按顺序出现;containsAllTermsInOrderPrefixLastTerm:同样按顺序,最后一个词可以只写前半截,适合边输入边搜。
  • wildcard:* 代表任意多个字符,? 代表一个字符。regex:正则表达式,必须匹配整个值;想找子串,就在两头加 .*。

中文不带空格,一整句就是一个词;想让两个词分开搜,就用空格隔开。词搜索不区分大小写,wildcard 和 regex 区分。

// 缺陷描述里同时提到「壳体」和「划痕」,且严重程度是 major 或 critical
client.objects("defect").where({
  description: { $containsAllTerms: "壳体 划痕" },
  severity: { $in: ["major", "critical"] },
})
随口一问

$containsAllTerms: "壳体划痕":整句被当成一个词,只找包含这整串字的描述。

好的交代

$containsAllTerms: "壳体 划痕":两个词,各自出现就算命中。

聚合与分组

聚合把一个对象集算成几个数。指标有 count、sum、avg、min、max、exactDistinct、approximateDistinct、approximatePercentile;再按属性分组,就是一张小表。分组有四种:

EXACT

exact

一个值一组,比如按产线。

FIXEDWIDTH

fixedWidth

数值按固定宽度分档,比如每 100 件一档。

RANGES

ranges

自己写区间,比如订单金额分三档。

DURATION

duration

日期和时间按天、周、月、季、年分组。

// 各产线已完工工单的合格数与报废数,报废多的排前面
const byLine = await client.objects("workOrder").where({ status: "done" }).aggregate({
  $select: { "goodQty:sum": "unordered", "scrapQty:sum": "desc" },
  $groupBy: { lineCode: "exact" },
});
// [ { $group: { lineCode: "L1" }, goodQty: { sum: 1200 }, scrapQty: { sum: 36 } }, … ]

// 每个月的订单数与订单总额
const byMonth = await client.objects("salesOrder").aggregate({
  $select: { $count: "unordered", "totalUsd:sum": "unordered" },
  $groupBy: { orderDate: { $duration: [1, "months"] } },
});

第一条查询在命令行里是这样:

aidc semantic aggregate workOrder --where '{"status":"done"}' \
  --select '{"goodQty:sum":"unordered","scrapQty:sum":"desc"}' --group-by '{"lineCode":"exact"}'

ranges 的每个区间含起点、不含终点,起点或终点不写就是开口;fixedWidth 的每一档从宽度的整数倍开始。

// 订单金额分三档:含起点,不含终点,最后一档没有上限
$groupBy: { totalUsd: { $ranges: [[0, 1000], [1000, 10000], [10000, undefined]] } }

几条规矩要记住。结果按第一个写了排序方向的指标排,没写就按分组值排。分组属性为空的对象不进任何一组。按天、按月分组按 UTC 切,北京时间月初的几个小时会算进上个月。

exact 分组的 maxGroupCount 缺省是 10,000,也是能设的最大值。一个属性有 25,000 种取值时,只返回 10,000 组,其余不在结果里。要看全,就先用 where 缩小范围,或者换更粗的分组。

聚合和派生属性分工不同。聚合回答「一共」,得到一张小表;派生属性回答「每个」,给每个对象加一个值。要一张按产线汇总的表,用聚合;要在客户列表里每行显示订单数,用 withProperties。

下一节把同样的问题交给 SQL,再看怎样按意思找相似的对象。

要点

  • where 是条件树,最多嵌套三层:叶子和每个 and、or、not 各算一层。
  • 同一个属性的几个值用 in,能少一层;SDK 的 $ne 会多占一层。
  • 文字搜索按词切分;要顺序用 InOrder,要前缀用 PrefixLastTerm,regex 必须匹配整个值。
  • 聚合有指标和四种分组:exact、fixedWidth、ranges、duration。
  • exact 分组最多返回 10,000 组,超出的不在结果里。

练一练

问工单和订单几个问题

用 workOrder、salesOrder 和 defect,先写 JS,再用 aidc semantic objects 或 aidc semantic aggregate 跑。

写出:产线是 L1 或 L2、状态是 running 或 paused、交期早于 2026-10-04 的工单。先画出条件树数层数,再用 in 改写成不超过两层。

小测

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

Q1下面哪棵条件树会因为超过嵌套上限被拒收?

Q2要算各产线的报废数合计,报废多的排前面,应该怎么写?

Q3对一个有 25,000 种取值的属性做 exact 分组,不写 maxGroupCount,会怎样?

延伸阅读