聚合

2.7.4

聚合是一种数据批处理的操作。聚合操作可以将数据分组(或者不分组,即只有一组/每个记录都是一组)然后对每组数据执行多种批处理操作,最后返回结果。有了聚合能力,可以方便的解决很多没有聚合能力时无法实现或只能低效实现的场景,这类场景的例子有:

  • 分组查询:比如按图书类别获取各类图书的平均销量,这对关系型数据库就是一个 groupBy + avg 的操作,但在现有能力下因没有分组能力和求统计值的能力,因此只能全量取数据后统计,既增加大量网络流量和延时又对本地算力和性能有较大消耗。
  • 只取某些字段的统计值或变换值返回:比如假设图书集合中每个图书记录中存放了一个数组字段代表每月销量,而此时我们想要获取每个图书的月平均销量,即希望取数组字段的平均值而不希望取多余数据再本地计算,这种场景下不使用聚合是无法实现的。
  • 流水线式分阶段批处理:比如求各图书类别的总销量最高的作者和最低的作者的操作,就涉及到先分组、再排序、再分组的分阶段的批处理操作,这种场景也是需要聚合能力才能完成的。
  • 获取唯一值(去重):比如获取某个类别的图书的所有作者名,需去重
  • …以下是一个最简的分组查询示例,采用上述分组查询引用的例子
  1. const db = wx.cloud.database()
  2. const $ = db.command.aggregate
  3. db.collection('books').aggregate()
  4. .group({
  5. // 按 category 字段分组
  6. _id: '$category',
  7. // 让输出的每组记录有一个 avgSales 字段,其值是组内所有记录的 sales 字段的平均值
  8. avgSales: $.avg('$sales')
  9. })
  10. .end()

上述代码首先将 books 集合的数据按 category 字段分组(分组后每组成为一个记录,_id 为分组所依据的字段值,其他字段都是统计值),然后分别取组内的 sales 字段的平均值。

聚合流水线

聚合是一个流水线式的批处理作业,一个流水线作业包含多个批处理阶段,每个阶段接收来自上一个阶段的输入记录列表(如果是第一个阶段则是集合全集)然后处理成新的记录列表后输出给下一个阶段,直至返回结果。

聚合阶段 & 操作符

一个聚合阶段是一个将一批输入记录按开发者指定的规则转换为新一批输出记录的过程。一个阶段的输出记录数与其输入记录数无关,即可以保持不变,每个输入记录对应一个输出记录,也可以合并或分组输出更少的一个或多个记录,甚至于输出更多的记录。一个聚合流水线操作的第一个阶段是流水线的开始,接收集合所有记录作为输入,最后一个阶段是流水线的结束,其结果作为输出返回给调用方。要定义一个阶段,首先要确定要使用的阶段,聚合能力提供了包括分组阶段 group、排序阶段 sort、投影阶段 project 等多种可选的阶段。每个阶段又可以通过一个对象作为参数定义这个阶段操作的具体行为表现,其中参数对象的每一个字段的值都必须是一个表达式(见下)或聚合操作符,一个操作符可以接收表达式作为输入(常量、字段引用等)。可用的操作符列表可以在文档中找到。

API

可查看文档一览所有的聚合 API,包括所有聚合流水线阶段、聚合操作符发起聚合操作发起聚合请求的接口。

表达式

在聚合中,一个表达式可以是字段(路径)引用、常量、对象表达式、或操作符表达式,并且可以嵌套使用表达式。

字段(路径)引用

通过字段(路径)引用可以引用一个字段的值,以一个 $ 开头的字符串代表字段(路径)引用,比如 $exam 表示引用 exam 字段,如果是嵌套字段或数组,也可以通过点表示法和数组下标表示法取引用,比如 $exam.math 表示引用 exam 字段对象下的 math 字段,$score[0] 表示引用数组字段 score 的第一个元素。

常量

可以是数字、字符串等常量,如果要使用一个以 $ 开头的字符串常量,需要使用 literal 表示这是一个常量而不是字段引用。

对象表达式

对象表达式即一个每个字段的值都是一个表达式的对象。

优化执行效率

因为每一个聚合操作是要输入整一个集合的数据的,因此有以下基本使用原则:

利用索引

matchsort 如果是在流水线的开头的话是可以利用索引的。geoNear 也可以利用地理位置索引,但要注意的是,geoNear 必须是流水线的第一个阶段。

尽早缩小数据集

只要需要的不是集合的全集,那就应该尽早的通过 matchlimitskip 缩小要处理的记录数量。

注意事项

match 参数语法同普通查询语法

除了 match 阶段,在各个聚合阶段中传入的对象可使用的操作符都是聚合操作符,需要特别注意的是,match 进行的是查询匹配,因此语法同普通查询(where)的语法,用的是普通查询操作符,示例可见 match API 文档

联表查询

使用 lookup 阶段可以完成联表查询。