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

本课目标
读完这一课,你将能够
- 写出带比较、文字、时间和组合条件的 where,并把嵌套控制在三层以内
- 在几种文字搜索里选对一种
- 写出带分组的聚合,并说出
maxGroupCount的上限意味着什么
where:一棵条件树
上一节的对象集里已经出现了 where;这一节把它讲清楚,再讲聚合。where 是一棵条件树:叶子是比较或搜索,枝是 and、or、not。它用在对象集的 filter 里,也用在 Search、Aggregate、Count 这几个接口里。
比较
eq、lt、lte、gt、gte、isNull、in;数组属性用 contains。
文字
containsAllTerms、containsAnyTerm、containsAllTermsInOrder、containsAllTermsInOrderPrefixLastTerm、wildcard、regex(startsWith 是旧别名)。
时间与位置
relativeDateRange:相对今天的日期范围;withinDistanceOf、withinBoundingBox:地理点在某个范围内。
组合
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
一个值一组,比如按产线。
fixedWidth
数值按固定宽度分档,比如每 100 件一档。
ranges
自己写区间,比如订单金额分三档。
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 改写成不超过两层。
按 orderDate 每月分组,求订单数和 totalUsd 之和;再用 ranges 把 totalUsd 分成 0–1,000、1,000–10,000、10,000 以上三档并计数。
在 defect.description 里找同时提到「壳体」和「划痕」的缺陷。把两个词中间的空格去掉再试一次,结果有什么不同?为什么?
小测
选一个答案,马上看解析。
Q1下面哪棵条件树会因为超过嵌套上限被拒收?
叶子算一层,and、or、not 各算一层:第二个是 and、or、not、eq 四层,超了;另外两个最多三层。
Q2要算各产线的报废数合计,报废多的排前面,应该怎么写?
按产线分组、对报废数求和,并让这个指标降序。第一个是按报废数的取值分组,第二个把产线编号当成数字分档,都不是要的结果。
Q3对一个有 25,000 种取值的属性做 exact 分组,不写 maxGroupCount,会怎样?
缺省和上限都是 10,000。取值更多时要先缩小对象集,或者换更粗的分组。