UNI-APP之uniCloud使用(聚合操做)| 8月更文挑战

聚合操做

获取数据库集合的聚合操做实例html

db.collection('scores').aggregate()
复制代码

addFields

聚合阶段。添加新字段到输出的记录。通过 addFields 聚合阶段,输出的全部记录中除了输入时带有的字段外,还将带有 addFields 指定的字段。sql

API 说明数据库

addFields 等同于同时指定了全部已有字段和新增字段的 project 阶段。express

addFields 的形式以下:json

addFields({  <新字段>: <表达式>})
复制代码

addFields 可指定多个新字段,每一个新字段的值由使用的表达式决定。数组

若是指定的新字段与原有字段重名,则新字段的值会覆盖原有字段的值。注意 addFields 不能用来给数组字段添加元素。markdown

示例 1:连续两次 addFieldsapp

假设集合 scores 有以下记录:oop

{
  _id: 1,
  student: "Maya",
  homework: [ 10, 5, 10 ],
  quiz: [ 10, 8 ],
  extraCredit: 0
}
{
  _id: 2,
  student: "Ryan",
  homework: [ 5, 6, 5 ],
  quiz: [ 8, 8 ],
  extraCredit: 8
}
复制代码

应用两次 addFields,第一次增长两个字段分别为 homeworkquiz 的和值,第二次增长一个字段再基于上两个和值求一次和值。ui

const $ = db.command.aggregate
db.collection('scores').aggregate()
  .addFields({
    totalHomework: $.sum('$homework'),
    totalQuiz: $.sum('$quiz')
  })
  .addFields({
    totalScore: $.add(['$totalHomework', '$totalQuiz', '$extraCredit'])
  })
  .end()
复制代码

返回结果以下:

{
  "_id" : 1,
  "student" : "Maya",
  "homework" : [ 10, 5, 10 ],
  "quiz" : [ 10, 8 ],
  "extraCredit" : 0,
  "totalHomework" : 25,
  "totalQuiz" : 18,
  "totalScore" : 43
}
{
  "_id" : 2,
  "student" : "Ryan",
  "homework" : [ 5, 6, 5 ],
  "quiz" : [ 8, 8 ],
  "extraCredit" : 8,
  "totalHomework" : 16,
  "totalQuiz" : 16,
  "totalScore" : 40
}
复制代码

示例 2:在嵌套记录里增长字段

能够用点表示法在嵌套记录里增长字段。假设 vehicles 集合含有以下记录:

{ _id: 1, type: "car", specs: { doors: 4, wheels: 4 } }
{ _id: 2, type: "motorcycle", specs: { doors: 0, wheels: 2 } }
{ _id: 3, type: "jet ski" }
复制代码

能够用以下操做在 specs 字段下增长一个新的字段 fuel_type,值都设为固定字符串 unleaded

db.collection('vehicles').aggregate()
  .addFields({
    'spec.fuel_type': 'unleaded'
  })
  .end()
复制代码

返回结果以下:

{ _id: 1, type: "car",
   specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } }
{ _id: 2, type: "motorcycle",
   specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } }
{ _id: 3, type: "jet ski",
   specs: { fuel_type: "unleaded" } }
复制代码

示例 3:设置字段值为另外一个字段

能够经过 $ 加字段名组成的字符串做为值的表达式来设置字段的值为另外一个字段的值。

一样用上一个集合示例,能够用以下操做添加一个字段 vehicle_type,将其值设置为 type 字段的值:

db.collection('vehicles').aggregate()
  .addFields({
    vehicle_type: '$type'
  })
  .end()
复制代码

返回结果以下:

{ _id: 1, type: "car", vehicle_type: "car",
   specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } }
{ _id: 2, type: "motorcycle", vehicle_type: "motorcycle",
   specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } }
{ _id: 3, type: "jet ski", vehicle_type: "jet ski",
   specs: { fuel_type: "unleaded" } }
复制代码

bucket

聚合阶段。将输入记录根据给定的条件和边界划分红不一样的组,每组即一个 bucket

API 说明

每组分别做为一个记录输出,包含一个如下界为值的 _id 字段和一个以组中记录数为值的 count 字段。count 在没有指定 output 的时候是默认输出的。

bucket 只会在组内有至少一个记录的时候输出。

bucket 的形式以下:

bucket({
  groupBy: <expression>,
  boundaries: [<lowerbound1>, <lowerbound2>, ...],
  default: <literal>,
  output: {
    <output1>: <accumulator expr>,
    ...
    <outputN>: <accumulator expr>
  }
})
复制代码

groupBy 是一个用以决定分组的表达式,会应用在各个输入记录上。能够用 $ 前缀加上要用以分组的字段路径来做为表达式。除非用 default 指定了默认值,不然每一个记录都须要包含指定的字段,且字段值必须在 boundaries 指定的范围以内。

boundaries 是一个数组,每一个元素分别是每组的下界。必须至少指定两个边界值。数组值必须是同类型递增的值。

default 可选,指定以后,没有进入任何分组的记录将都进入一个默认分组,这个分组记录的 _id 即由 default 决定。default 的值必须小于 boundaries 中的最小值或大于等于其中的最大值。default 的值能够与 boundaries 元素值类型不一样。

output 可选,用以决定输出记录除了 _id 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 output 指定时,默认的 count 是不会被默认输出的,必须手动指定:

output: {  count: $.sum(1),  ...  <outputN>: <accumulator expr>}
复制代码

使用 bucket 须要知足如下至少一个条件,不然会抛出错误:

每个输入记录应用 groupBy 表达式获取的值都必须是一个在 boundaries 内的值

指定一个 default 值,该值在 boundaries 之外,或与 boundaries 元素的值不一样的类型。

示例

假设集合 items 有以下记录:

{
  _id: "1",
  price: 10
}
{
  _id: "2",
  price: 50
}
{
  _id: "3",
  price: 20
}
{
  _id: "4",
  price: 80
}
{
  _id: "5",
  price: 200
}
复制代码

对上述记录进行分组,将 [0, 50) 分为一组,[50, 100) 分为一组,其余分为一组:

const $ = db.command.aggregate
db.collection('items').aggregate()
  .bucket({
    groupBy: '$price',
    boundaries: [0, 50, 100],
    default: 'other',
    output: {
      count: $.sum(),
      ids: $.push('$_id')
    }
  })
  .end()
复制代码

返回结果以下:

[
  {
    "_id": 0,
    "count": 2,
    "ids": [
      "1",
      "3"
    ]
  },
  {
    "_id": 50,
    "count": 2,
    "ids": [
      "2",
      "4"
    ]
  },
  {
    "_id": "other",
    "count": 22,
    "ids": [
      "5"
    ]
  }
]
复制代码

bucketAuto

聚合阶段。将输入记录根据给定的条件划分红不一样的组,每组即一个 bucket。与 bucket 的其中一个不一样之处在于无需指定 boundariesbucketAuto 会自动尝试将记录尽量平均的分散到每组中。

API 说明每组分别做为一个记录输出,包含一个以包含组中最大值和最小值两个字段的对象为值的 _id 字段和一个以组中记录数为值的 count 字段。count 在没有指定 output 的时候是默认输出的。

bucketAuto 的形式以下:

bucketAuto({
  groupBy: <expression>,
  buckets: <number>,
  granularity: <string>,
  output: {
    <output1>: <accumulator expr>,
    ...
    <outputN>: <accumulator expr>
  }
})
复制代码

groupBy 是一个用以决定分组的表达式,会应用在各个输入记录上。能够用 $ 前缀加上要用以分组的字段路径来做为表达式。除非用 default 指定了默认值,不然每一个记录都须要包含指定的字段,且字段值必须在 boundaries 指定的范围以内。

buckets 是一个用于指定划分组数的正整数。

granularity 是可选枚举值字符串,用于保证自动计算出的边界符合给定的规则。这个字段仅可在全部 groupBy 值都是数字而且没有 NaN 的状况下使用。枚举值包括:R五、R十、R20、R40、R80、1-2-五、E六、E十二、E2四、E4八、E9六、E19二、POWERSOF2

output 可选,用以决定输出记录除了 _id 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 output 指定时,默认的 count 是不会被默认输出的,必须手动指定:

output: {  count: $.sum(1),  ...  <outputN>: <accumulator expr>}
复制代码

在如下状况中,输出的分组可能会小于给定的组数:

输入记录数少于分组数

  • groupBy 计算获得的惟一值少于分组数
  • granularity 的间距少于分组数
  • granularity 不够精细以致于不能平均分配到各组

granularity 详细说明

granularity 用于保证边界值属于一个给定的数字序列。

Renard 序列

Renard 序列是以 10 的 5 / 10 / 20 / 40 / 80 次方根来推导的、在 1.0 到 10.0 (若是是 R80 则是 10.3) 之间的数字序列。

设置 granularity 为 R5 / R10 / R20 / R40 / R80 就把边界值限定在序列内。若是 groupBy 的值不在 1.0 到 10.0 (若是是 R80 则是 10.3) 内,则序列数字会自动乘以 10。

E 序列

E 序列是以 10 的 6 / 12 / 24 / 48 / 96 / 192 次方跟来推导的、带有一个特定偏差的、在 1.0 到 10.0 之间的数字序列。

1-2-5 序列

1-2-5 序列 表现与三值 Renard 序列同样。

2的次方序列

由 2 的各次方组成的序列数字。

示例

假设集合 items 有以下记录:

{
  _id: "1",
  price: 10.5
}
{
  _id: "2",
  price: 50.3
}
{
  _id: "3",
  price: 20.8
}
{
  _id: "4",
  price: 80.2
}
{
  _id: "5",
  price: 200.3
}
复制代码

对上述记录进行自动分组,分红三组:

const $ = db.command.aggregate
db.collection('items').aggregate()
  .bucket({
    groupBy: '$price',
    buckets: 3,
  })
  .end()
复制代码

返回结果以下:

{
  "_id": {
    "min": 10.5,
    "max": 50.3
  },
  "count": 2
}
{
  "_id": {
    "min": 50.3,
    "max": 200.3
  },
  "count": 2
}
{
  "_id": {
    "min": 200.3,
    "max": 200.3
  },
  "count": 1
}
复制代码

count

聚合阶段。计算上一聚合阶段输入到本阶段的记录数,输出一个记录,其中指定字段的值为记录数。

API 说明

count 的形式以下:

count(<string>)
复制代码

是输出记录数的字段的名字,不能是空字符串,不能以 $ 开头,不能包含 . 字符。

count 阶段等同于 group + project 的操做:

const $ = db.command.aggregate
db.collection('items').aggregate()
  .group({
    _id: null,
    count: $.sum(1),
  })
  .project({
    _id: 0,
  })
  .end()
复制代码

上述操做会输出一个包含 count 字段的记录。

示例

假设集合 items 有以下记录:

{
  _id: "1",
  price: 10.5
}
{
  _id: "2",
  price: 50.3
}
{
  _id: "3",
  price: 20.8
}
{
  _id: "4",
  price: 80.2
}
{
  _id: "5",
  price: 200.3
}
复制代码

找出价格大于 50 的记录数:

const $ = db.command.aggregate
db.collection('items').aggregate()
  .match({
    price: $.gt(50)
  })
  .count('expensiveCount')
  .end()
复制代码

返回结果以下:

{  "expensiveCount": 3}
复制代码

group

聚合阶段。将输入记录按给定表达式分组,输出时每一个记录表明一个分组,每一个记录的 _id 是区分不一样组的 key。输出记录中也能够包括累计值,将输出字段设为累计值即会从该分组中计算累计值。

API 说明

group 的形式以下:

group({
  _id: <expression>,
  <field1>: <accumulator1>,
  ...
  <fieldN>: <accumulatorN>
})
复制代码

_id 参数是必填的,若是填常量则只有一组。其余字段是可选的,都是累计值,用 $.sum 等累计器(const $ = db.command.aggregate),但也可使用其余表达式。

累计器必须是如下操做符之一:

详细使用方法见累计器操做符

操做符 说明
addToSet 向数组中添加值,若是数组中已存在该值,不执行任何操做
avg 返回一组集合中,指定字段对应数据的平均值
sum 计算而且返回一组字段全部数值的总和
first 返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操做才有意义。
last 返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操做才有意义。
max 返回一组数值的最大值
min 返回一组数值的最小值
push 在 group 阶段,返回一组中表达式指定列与对应的值,一块儿组成的数组
stdDevPop 返回一组字段对应值的标准差
stdDevSamp 计算输入值的样本标准误差。若是输入值表明数据整体,或者不归纳更多的数据,请改用 db.command.aggregate.stdDevPop
mergeObjects 将多个文档合并为单个文档

内存限制

该阶段有 100M 内存使用限制。

示例 1:按字段值分组

假设集合 avatar 有以下记录:

{
  _id: "1",
  alias: "john",
  region: "asia",
  scores: [40, 20, 80],
  coins: 100
}
{
  _id: "2",
  alias: "arthur",
  region: "europe",
  scores: [60, 90],
  coins: 20
}
{
  _id: "3",
  alias: "george",
  region: "europe",
  scores: [50, 70, 90],
  coins: 50
}
{
  _id: "4",
  alias: "john",
  region: "asia",
  scores: [30, 60, 100, 90],
  coins: 40
}
{
  _id: "5",
  alias: "george",
  region: "europe",
  scores: [20],
  coins: 60
}
{
  _id: "6",
  alias: "john",
  region: "asia",
  scores: [40, 80, 70],
  coins: 120
}
复制代码
const $ = db.command.aggregate
db.collection('avatar').aggregate()
  .group({
    _id: '$alias',
    num: $.sum(1)
  })
  .end()
复制代码

返回结果以下:

{
  "_id": "john",
  "num": 3
}
{
  "_id": "authur",
  "num": 1
}
{
  "_id": "george",
  "num": 2
}
复制代码

示例 2:按多个值分组

能够给 _id 传入记录的方式按多个值分组。仍是沿用上面的示例数据,按各个区域(region)得到相同最高分(score)的来分组,并求出各组虚拟币(coins)的总量:

const $ = db.command.aggregate
db.collection('avatar').aggregate()
  .group({
    _id: {
      region: '$region',
      maxScore: $.max('$scores')
    },
    totalCoins: $.sum('$coins')
  })
  .end()
复制代码

返回结果以下:

{
  "_id": {
    "region": "asia",
    "maxScore": 80
  },
  "totalCoins": 220
}
{
  "_id": {
    "region": "asia",
    "maxScore": 100
  },
  "totalCoins": 100
}
{
  "_id": {
    "region": "europe",
    "maxScore": 90
  },
  "totalCoins": 70
}
{
  "_id": {
    "region": "europe",
    "maxScore": 20
  },
  "totalCoins": 60
}
复制代码

limit

聚合阶段。限制输出到下一阶段的记录数。

示例

假设集合 items 有以下记录:

{
  _id: "1",
  price: 10
}
{
  _id: "2",
  price: 50
}
{
  _id: "3",
  price: 20
}
{
  _id: "4",
  price: 80
}
{
  _id: "5",
  price: 200
}
复制代码

返回价格大于 20 的记录的最小的两个记录:

const $ = db.command.aggregate
db.collection('items').aggregate()
  .match({
    price: $.gt(20)
  })
  .sort({
    price: 1,
  })
  .limit(2)
  .end()
复制代码

返回结果以下:

{
  "_id": "3",
  "price": 20
}
{
  "_id": "4",
  "price": 80
}
复制代码

lookup

聚合阶段。联表查询。与同个数据库下的一个指定的集合作 left outer join(左外链接)。对该阶段的每个输入记录,lookup 会在该记录中增长一个数组字段,该数组是被联表中知足匹配条件的记录列表。lookup 会将链接后的结果输出给下个阶段。

API 说明

lookup 有两种使用方式

相等匹配

将输入记录的一个字段和被链接集合的一个字段进行相等匹配时,采用如下定义:

lookup({
  from: <要链接的集合名>,
  localField: <输入记录的要进行相等匹配的字段>,
  foreignField: <被链接集合的要进行相等匹配的字段>,
  as: <输出的数组字段名>
})
复制代码

参数详细说明

参数字段 说明
from 要进行链接的另一个集合的名字
localField 当前流水线的输入记录的字段名,该字段将被用于与 from 指定的集合的 foreignField 进行相等匹配。若是输入记录中没有该字段,则该字段的值在匹配时会被视做 null
foreignField 被链接集合的字段名,该字段会被用于与 localField 进行相等匹配。若是被链接集合的记录中没有该字段,该字段的值将在匹配时被视做 null
as 指定链接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。若是输入记录中原本就已有该字段,则该字段会被覆写

这个操做等价于如下伪 SQL 操做:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
                               FROM <collection to join>
                               WHERE <foreignField>= <collection.localField>);
复制代码

例子:

  • 指定一个相等匹配条件
  • 对数组字段应用相等匹配
  • 组合 mergeObjects 应用相等匹配

自定义链接条件、拼接子查询

此用法阿里云暂不支持

若是须要指定除相等匹配以外的链接条件,或指定多个相等匹配条件,或须要拼接被链接集合的子查询结果,那可使用以下定义:

lookup({
  from: <要链接的集合名>,
  let: { <变量1>: <表达式1>, ..., <变量n>: <表达式n> },
  pipeline: [ <在要链接的集合上进行的流水线操做> ],
  as: <输出的数组字段名>
})
复制代码

参数详细说明

参数字段 说明
from 要进行链接的另一个集合的名字
let 可选。指定在 pipeline 中可使用的变量,变量的值能够引用输入记录的字段,好比 let: { userName: '$name' } 就表明将输入记录的 name 字段做为变量 userName 的值。在 pipeline 中没法直接访问输入记录的字段,必须经过 let 定义以后才能访问,访问的方式是在 expr 操做符中用 变量名的方式访问,好比 变量名 的方式访问,好比 userName。
pipeline 指定要在被链接集合中运行的聚合操做。若是要返回整个集合,则该字段取值空数组 []。在 pipeline 中没法直接访问输入记录的字段,必须经过 let 定义以后才能访问,访问的方式是在 expr 操做符中用 变量名的方式访问,好比 变量名 的方式访问,好比 userName。
as 指定链接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。若是输入记录中原本就已有该字段,则该字段会被覆写

该操做等价于如下伪 SQL 语句:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT <documents as determined from the pipeline>
                               FROM <collection to join>
                               WHERE <pipeline> );
复制代码

例子

  • 指定多个链接条件
  • 拼接被链接集合的子查询

示例

指定一个相等匹配条件

假设 orders 集合有如下记录:

[
  {"_id":4,"book":"novel 1","price":30,"quantity":2},
  {"_id":5,"book":"science 1","price":20,"quantity":1},
  {"_id":6}
]
复制代码

books 集合有如下记录:

[
  {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"},
  {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"},
  {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"},
  {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"},
  {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null},
  {"_id":"book6","author":"author 5","category":"novel","stock":"60"}
]
复制代码

如下聚合操做能够经过一个相等匹配条件链接 ordersbooks 集合,匹配的字段是 orders 集合的 book 字段和 books 集合的 title 字段:

const db = cloud.database()
db.collection('orders').aggregate()
  .lookup({
    from: 'books',
    localField: 'book',
    foreignField: 'title',
    as: 'bookList',
  })
  .end()
  .then(res => console.log(res))
  .catch(err => console.error(err))
复制代码

结果:

[
  {
    "_id": 4,
    "book": "novel 1",
    "price": 30,
    "quantity": 2,
    "bookList": [
      {
        "_id": "book1",
        "title": "novel 1",
        "author": "author 1",
        "category": "novel",
        "stock": 10
      }
    ]
  },
  {
    "_id": 5,
    "book": "science 1",
    "price": 20,
    "quantity": 1,
    "bookList": [
      {
        "_id": "book3",
        "category": "science",
        "title": "science 1",
        "author": "author 3",
        "stock": 30
      }
    ]
  },
  {
    "_id": 6,
    "bookList": [
      {
        "_id": "book5",
        "category": "science",
        "author": "author 4",
        "stock": 50,
        "title": null
      },
      {
        "_id": "book6",
        "author": "author 5",
        "stock": "60",
        "category": "novel"
      }
    ]
  }
]
复制代码

对数组字段应用相等匹配假设 authors 集合有如下记录:

[
  {"_id": 1, "name": "author 1", "intro": "Two-time best-selling sci-fiction novelist"},
  {"_id": 3, "name": "author 3", "intro": "UCB assistant professor"},
  {"_id": 4, "name": "author 4", "intro": "major in CS"}
]
复制代码

books 集合有如下记录:

[
  {"_id":"book1","authors":["author 1"],"category":"novel","stock":10,"time":1564456048486,"title":"novel 1"},
  {"_id":"book3","authors":["author 3", "author 4"],"category":"science","stock":30,"title":"science 1"},
  {"_id":"book4","authors":["author 3"],"category":"science","stock":40,"title":"science 2"}
]
复制代码

如下操做获取做者信息及他们分别发表的书籍,使用了 lookup 操做匹配 authors 集合的 name 字段和 books 集合的 authors 数组字段:

const db = cloud.database()
db.collection('authors').aggregate()
  .lookup({
    from: 'books',
    localField: 'name',
    foreignField: 'authors',
    as: 'publishedBooks',
  })
  .end()
  .then(res => console.log(res))
  .catch(err => console.error(err))
复制代码

结果

[
  {
    "_id": 1,
    "intro": "Two-time best-selling sci-fiction novelist",
    "name": "author 1",
    "publishedBooks": [
      {
        "_id": "book1",
        "title": "novel 1",
        "category": "novel",
        "stock": 10,
        "authors": [
          "author 1"
        ]
      }
    ]
  },
  {
    "_id": 3,
    "name": "author 3",
    "intro": "UCB assistant professor",
    "publishedBooks": [
      {
        "_id": "book3",
        "category": "science",
        "title": "science 1",
        "stock": 30,
        "authors": [
          "author 3",
          "author 4"
        ]
      },
      {
        "_id": "book4",
        "title": "science 2",
        "category": "science",
        "stock": 40,
        "authors": [
          "author 3"
        ]
      }
    ]
  },
  {
    "_id": 4,
    "intro": "major in CS",
    "name": "author 4",
    "publishedBooks": [
      {
        "_id": "book3",
        "category": "science",
        "title": "science 1",
        "stock": 30,
        "authors": [
          "author 3",
          "author 4"
        ]
      }
    ]
  }
]
复制代码

组合 mergeObjects 应用相等匹配

假设 orders 集合有如下记录:

[
  {"_id":4,"book":"novel 1","price":30,"quantity":2},
  {"_id":5,"book":"science 1","price":20,"quantity":1},
  {"_id":6}
]
复制代码

books 集合有如下记录:

[
  {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"},
  {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"},
  {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"},
  {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"},
  {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null},
  {"_id":"book6","author":"author 5","category":"novel","stock":"60"}
]
复制代码

如下操做匹配 orders 的 book 字段和 books 的 title 字段,并将 books 匹配结果直接 merge 到 orders 记录中。

var db = cloud.database()
var $ = db.command.aggregate
db.collection('orders').aggregate()
  .lookup({
    from: "books",
    localField: "book",
    foreignField: "title",
    as: "bookList"
  })
  .replaceRoot({
    newRoot: $.mergeObjects([ $.arrayElemAt(['$bookList', 0]), '$$ROOT' ])
  })
  .project({
    bookList: 0
  })
  .end()
  .then(res => console.log(res))
  .catch(err => console.error(err))
复制代码

结果

[
  {
    "_id": 4,
    "title": "novel 1",
    "author": "author 1",
    "category": "novel",
    "stock": 10,
    "book": "novel 1",
    "price": 30,
    "quantity": 2
  },
  {
    "_id": 5,
    "category": "science",
    "title": "science 1",
    "author": "author 3",
    "stock": 30,
    "book": "science 1",
    "price": 20,
    "quantity": 1
  },
  {
    "_id": 6,
    "category": "science",
    "author": "author 4",
    "stock": 50,
    "title": null
  }
]
复制代码

指定多个链接条件

假设 orders 集合有如下记录:

[
  {"_id":4,"book":"novel 1","price":300,"quantity":20},
  {"_id":5,"book":"science 1","price":20,"quantity":1}
]
复制代码

books 集合有如下记录:

[
  {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"},
  {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}
]
复制代码

如下操做链接 ordersbooks 集合,要求两个条件:

  • orders 的 book 字段与 books 的 title 字段相等
  • orders 的 quantity 字段大于或等于 books 的 stock 字段
const db = cloud.database()
const $ = db.command.aggregate
db.collection('orders').aggregate()
.lookup({
  from: 'books',
  let: {
    order_book: '$book',
    order_quantity: '$quantity'
  },
  pipeline: $.pipeline()
    .match(_.expr($.and([
      $.eq(['$title', '$$order_book']),
      $.gte(['$stock', '$$order_quantity'])
    ])))
    .project({
      _id: 0,
      title: 1,
      author: 1,
      stock: 1
    })
    .done(),
  as: 'bookList',
})
.end()
.then(res => console.log(res))
.catch(err => console.error(err))
复制代码

结果:

[
{
  "_id": 4,
  "book": "novel 1",
  "price": 300,
  "quantity": 20,
  "bookList": []
},
{
  "_id": 5,
  "book": "science 1",
  "price": 20,
  "quantity": 1,
  "bookList": [
    {
      "title": "science 1",
      "author": "author 3",
      "stock": 30
    }
  ]
}
]
复制代码

拼接被链接集合的子查询

假设 orders 集合有如下记录:

[
  {"_id":4,"book":"novel 1","price":30,"quantity":2},
  {"_id":5,"book":"science 1","price":20,"quantity":1}
]
复制代码

books 集合有如下记录:

[
  {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"},
  {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"},
  {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"}
]
复制代码

在每条输出记录上加上一个数组字段,该数组字段的值是对 books 集合的一个查询语句的结果:

const db = cloud.database()
const $ = db.command.aggregate
db.collection('orders').aggregate()
  .lookup({
    from: 'books',
    let: {
      order_book: '$book',
      order_quantity: '$quantity'
    },
    pipeline: $.pipeline()
      .match({
        author: 'author 3'
      })
      .project({
        _id: 0,
        title: 1,
        author: 1,
        stock: 1
      })
      .done(),
    as: 'bookList',
  })
  .end()
  .then(res => console.log(res))
  .catch(err => console.error(err))
复制代码

结果

[
  {
    "_id": 4,
    "book": "novel 1",
    "price": 30,
    "quantity": 20,
    "bookList": [
      {
        "title": "science 1",
        "author": "author 3",
        "stock": 30
      },
      {
        "title": "science 2",
        "author": "author 3",
        "stock": 40
      }
    ]
  },
  {
    "_id": 5,
    "book": "science 1",
    "price": 20,
    "quantity": 1,
    "bookList": [
      {
        "title": "science 1",
        "author": "author 3",
        "stock": 30
      },
      {
        "title": "science 2",
        "author": "author 3",
        "stock": 40
      }
    ]
  }
]
复制代码

match

聚合阶段。根据条件过滤文档,而且把符合条件的文档传递给下一个流水线阶段。

API 说明

match 的形式以下:

match(<查询条件>)
复制代码

查询条件与普通查询一致,能够用普通查询操做符,注意 match 阶段和其余聚合阶段不一样,不可以使用聚合操做符,只能使用查询操做符。

// 直接使用字符串
match({
  name: 'Tony Stark'
})
复制代码
// 使用操做符
const _ = db.command
match({
  age: _.gt(18)
})
复制代码

示例

假设集合 articles 有以下记录:

{ "_id" : "1", "author" : "stark",  "score" : 80 }
{ "_id" : "2", "author" : "stark",  "score" : 85 }
{ "_id" : "3", "author" : "bob",    "score" : 60 }
{ "_id" : "4", "author" : "li",     "score" : 55 }
{ "_id" : "5", "author" : "jimmy",  "score" : 60 }
{ "_id" : "6", "author" : "li",     "score" : 94 }
{ "_id" : "7", "author" : "justan", "score" : 95 }
复制代码

匹配

下面是一个直接匹配的例子:

db.collection('articles')
  .aggregate()
  .match({
    author: 'stark'
  })
  .end()
复制代码

这里的代码尝试找到全部 author 字段是 stark 的文章,那么匹配以下:

{ "_id" : "1", "author" : "stark", "score" : 80 }
{ "_id" : "2", "author" : "stark", "score" : 85 }
复制代码

计数

match 过滤出文档后,还能够与其余流水线阶段配合使用。

好比下面这个例子,咱们使用 group 进行搭配,计算 score 字段大于 80 的文档数量:

const _ = db.command
const $ = _.aggregate
db.collection('articles')
  .aggregate()
  .match({
    score: _.gt(80)
  })
  .group({
      _id: null,
      count: $.sum(1)
  })
  .end()
复制代码

返回值以下:

{ "_id" : null, "count" : 3 }
复制代码

project

聚合阶段。把指定的字段传递给下一个流水线,指定的字段能够是某个已经存在的字段,也能够是计算出来的新字段。

API 说明

project 的形式以下:

project({  <表达式>})
复制代码

表达式能够有如下格式:

格式 说明
<字段>: <1 或 true> 指定包含某个已有字段
_id: <0 或 false> 舍弃 _id 字段
<字段>: <表达式> 加入一个新字段,或者重置某个已有字段
<字段>: <0 或 false> 舍弃某个字段(若是你指定舍弃了某个非 _id 字段,那么在这次 project 中,你不能再使用其它表达式)

指定包含字段

_id 字段是默认包含在输出中的,除此以外其余任何字段,若是想要在输出中体现的话,必须在 project 中指定; 若是指定包含一个尚不存在的字段,那么 project 会忽略这个字段,不会加入到输出的文档中;

指定排除字段

若是你在 project 中指定排除某个字段,那么其它字段都会体如今输出中; 若是指定排除的是非 _id 字段,那么在本次 project 中,不能再使用其它表达式;

加入新的字段或重置某个已有字段

你可使用一些特殊的表达式加入新的字段,或重置某个已有字段。

多层嵌套的字段

有时有些字段处于多层嵌套的底层,咱们可使用点记法:

"contact.phone.number": <1 or 0 or 表达式>
复制代码

也能够直接使用嵌套的格式:

contact: { phone: { number: <1 or 0 or 表达式> } }
复制代码

示例

假设咱们有一个 articles 集合,其中含有如下文档:

{
    "_id": 666,
    "title": "This is title",
    "author": "Nobody",
    "isbn": "123456789",
    "introduction": "......"
}
复制代码

指定包含某些字段

下面的代码使用 project,让输出只包含 _id、title 和 author 字段:

db.collection('articles')
  .aggregate()
  .project({
    title: 1,
    author: 1
  })
  .end()
复制代码

输出以下:

{ "_id" : 666, "title" : "This is title", "author" : "Nobody" }
复制代码

去除输出中的 _id 字段

_id 是默认包含在输出中的,若是不想要它,能够指定去除它:

db.collection('articles')
  .aggregate()
  .project({
    _id: 0,  // 指定去除 _id 字段
    title: 1,
    author: 1
  })
  .end()
复制代码

输出以下:

{ "title" : "This is title", "author" : "Nobody" }
复制代码

去除某个非 _id 字段

咱们还能够指定在输出中去掉某个非 _id 字段,这样其它字段都会被输出:

db.collection('articles')
  .aggregate()
  .project({
    isbn: 0,  // 指定去除 isbn 字段
  })
  .end()
复制代码

输出以下,相比输入,没有了 isbn 字段:

{
    "_id" : 666,
    "title" : "This is title",
    "author" : "Nobody",
    "introduction": "......"
}
复制代码

加入计算出的新字段

假设咱们有一个 students 集合,其中包含如下文档:

{
    "_id": 1,
    "name": "小明",
    "scores": {
        "chinese": 80,
        "math": 90,
        "english": 70
    }
}
复制代码

下面的代码,咱们使用 project,在输出中加入了一个新的字段 totalScore:

const { sum } = db.command.aggregate
db.collection('students')
  .aggregate()
  .project({
    _id: 0,
    name: 1,
    totalScore: sum([
        "$scores.chinese",
        "$scores.math",
        "$scores.english"
    ])
  })
  .end()
复制代码

输出为:

{ "name": "小明", "totalScore": 240 }
复制代码

加入新的数组字段

假设咱们有一个 points 集合,包含如下文档:

{ "_id": 1, "x": 1, "y": 1 }
{ "_id": 2, "x": 2, "y": 2 }
{ "_id": 3, "x": 3, "y": 3 }
复制代码

下面的代码,咱们使用 project,把 x 和 y 字段,放入到一个新的数组字段 coordinate 中:

db.collection('points')
  .aggregate()
  .project({
    coordinate: ["$x", "$y"]
  })
  .end()
复制代码

输出以下:

{ "_id": 1, "coordinate": [1, 1] }
{ "_id": 2, "coordinate": [2, 2] }
{ "_id": 3, "coordinate": [3, 3] }
复制代码

replaceRoot

聚合阶段。指定一个已有字段做为输出的根节点,也能够指定一个计算出的新字段做为根节点。

API 说明

replaceRoot 使用形式以下:

replaceRoot({    newRoot: <表达式>})
复制代码

表达式格式以下:

格式 说明
<字段名> 指定一个已有字段做为输出的根节点(若是字段不存在则报错)
<对象> 计算一个新字段,而且把这个新字段做为根节点

示例

使用已有字段做为根节点

假设咱们有一个 schools 集合,内容以下:

{
  "_id": 1,
  "name": "SFLS",
  "teachers": {
    "chinese": 22,
    "math": 18,
    "english": 21,
    "other": 123
  }
}
复制代码

下面的代码使用 replaceRoot,把 teachers 字段做为根节点输出:

db.collection('schools')
  .aggregate()
  .replaceRoot({
    newRoot: '$teachers'
  })
  .end()
复制代码

输出以下:

{
  "chinese": 22,
  "math": 18,
  "english": 21,
  "other": 123
}
复制代码

使用计算出的新字段做为根节点

假设咱们有一个 roles 集合,内容以下:

{ "_id": 1, "first_name": "四郎", "last_name": "黄" }
{ "_id": 2, "first_name": "邦德", "last_name": "马" }
{ "_id": 3, "first_name": "牧之", "last_name": "张" }
复制代码

下面的代码使用 replaceRoot,把 first_name 和 last_name 拼在一块儿:

const { concat } = db.command.aggregate
db.collection('roles')
  .aggregate()
  .replaceRoot({
    newRoot: {
      full_name: concat(['$last_name', '$first_name'])
    }
  })
  .end()
复制代码

输出以下:

{ "full_name": "黄四郎" }
{ "full_name": "马邦德" }
{ "full_name": "张牧之" }
复制代码

sample

聚合阶段。随机从文档中选取指定数量的记录。

API 说明

sample 的形式以下:

sample({    size: <正整数>})
复制代码

请注意:size 是正整数,不然会出错。

示例

假设文档 users 有如下记录:

{ "name": "a" }{ "name": "b" }
复制代码

随机选取

若是如今进行抽奖活动,须要选出一名幸运用户。那么 sample 的调用方式以下:

db.collection('users')
  .aggregate()
  .sample({
    size: 1
  })
  .end()
复制代码

返回了随机选中的一个用户对应的记录,结果以下:

{ "_id": "696529e4-7e82-4e7f-812e-5144714edff6", "name": "b" }
复制代码

skip

聚合阶段。指定一个正整数,跳过对应数量的文档,输出剩下的文档。

示例

db.collection('users')
  .aggregate()
  .skip(5)
  .end()
复制代码

这段代码会跳过查找到的前 5 个文档,而且把剩余的文档输出。

sort

聚合阶段。根据指定的字段,对输入的文档进行排序。

API 说明

形式以下:

sort({
    <字段名1>: <排序规则>,
    <字段名2>: <排序规则>,
})
复制代码

<排序规则>能够是如下取值:

  • 1 表明升序排列(从小到大);
  • -1 表明降序排列(从大到小);

示例

升序/降序排列

假设咱们有集合 articles,其中包含数据以下:

{ "_id": "1", "author": "stark",  "score": 80, "age": 18 }
{ "_id": "2", "author": "bob",    "score": 60, "age": 18 }
{ "_id": "3", "author": "li",     "score": 55, "age": 19 }
{ "_id": "4", "author": "jimmy",  "score": 60, "age": 22 }
{ "_id": "5", "author": "justan", "score": 95, "age": 33 }
复制代码

上面的代码在 students 集合中进行聚合搜索,而且将结果排序,首先根据 age 字段降序排列,而后再根据 score 字段进行降序排列。

输出结果以下:

db.collection('articles')
  .aggregate()
  .sort({
      age: -1,
      score: -1
  })
  .end()
复制代码

sortByCount

聚合阶段。根据传入的表达式,将传入的集合进行分组(group)。而后计算不一样组的数量,而且将这些组按照它们的数量进行排序,返回排序后的结果。

API 说明

sortByCount 的调用方式以下:

sortByCount(<表达式>)
复制代码

表达式的形式是: + 指定字段。请注意:不要漏写 + 指定字段。请注意:不要漏写 符号。

示例

统计基础类型

假设集合 passages 的记录以下:

{ "category": "Web" }
{ "category": "Web" }
{ "category": "Life" }
复制代码

下面的代码就能够统计文章的分类信息,而且计算每一个分类的数量。即对 category 字段执行 sortByCount 聚合操做。

db.collection('passages')
  .aggregate()
  .sortByCount('$category')
  .end()
复制代码

返回的结果以下所示:Web 分类下有2篇文章,Life 分类下有1篇文章。

{ "_id": "Web", "count": 2 }
{ "_id": "Life", "count": 1 }
复制代码

解构数组类型

假设集合 passages 的记录以下:tags 字段对应的值是数组类型。

{ "tags": [ "JavaScript", "C#" ] }
{ "tags": [ "Go", "C#" ] }
{ "tags": [ "Go", "Python", "JavaScript" ] }
复制代码

如何统计文章的标签信息,而且计算每一个标签的数量?由于 tags 字段对应的数组,因此须要借助 unwind 操做解构 tags 字段,而后再调用 sortByCount。

下面的代码实现了这个功能:

db.collection('passages')
  .aggregate()
  .unwind(`$tags`)
  .sortByCount(`$tags`)
  .end()
复制代码

返回的结果以下所示:

{ "_id": "Go", "count": 2 }
{ "_id": "C#", "count": 2 }
{ "_id": "JavaScript", "count": 2 }
{ "_id": "Python", "count": 1 }
复制代码

unwind

聚合阶段。使用指定的数组字段中的每一个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每一个元素。

API 说明

使用指定的数组字段中的每一个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每一个元素。

unwind 有两种使用形式:

参数是一个字段名

unwind(<字段名>)
复制代码

参数是一个对象

unwind({
    path: <字段名>,
    includeArrayIndex: <string>,
    preserveNullAndEmptyArrays: <boolean>
})
复制代码
字段 类型 说明
path string 想要拆分的数组的字段名,须要以 $ 开头。
includeArrayIndex string 可选项,传入一个新的字段名,数组索引会保存在这个新的字段上。新的字段名不能以 $ 开头。
preserveNullAndEmptyArrays boolean 若是为 true,那么在 path 对应的字段为 null、空数组或者这个字段不存在时,依然会输出这个文档;若是为 false,unwind 将不会输出这些文档。默认为 false。

示例

拆分数组

假设咱们有一个 products 集合,包含数据以下:

{ "_id": "1", "product": "tshirt", "size": ["S", "M", "L"] }
{ "_id": "2", "product": "pants", "size": [] }
{ "_id": "3", "product": "socks", "size": null }
{ "_id": "4", "product": "trousers", "size": ["S"] }
{ "_id": "5", "product": "sweater", "size": ["M", "L"] }
复制代码

咱们根据 size 字段对这些文档进行拆分

db.collection('products')
  .aggregate()
  .unwind('$size')
  .end()
复制代码

输出以下:

{ "_id": "1", "product": "tshirt", "size": "S" }
{ "_id": "1", "product": "tshirt", "size": "M" }
{ "_id": "1", "product": "tshirt", "size": "L" }
{ "_id": "4", "product": "trousers", "size": "S" }
{ "_id": "5", "product": "sweater", "size": "M" }
{ "_id": "5", "product": "sweater", "size": "L" }
复制代码

拆分后,保留原数组的索引

咱们根据 size 字段对文档进行拆分后,想要保留原数组索引在新的 index 字段中。

db.collection('products')
  .aggregate()
  .unwind({
      path: '$size',
      includeArrayIndex: 'index'
  })
  .end()
复制代码

输出以下:

{ "_id": "1", "product": "tshirt", "size": "S", "index": 0 }
{ "_id": "1", "product": "tshirt", "size": "M", "index": 1 }
{ "_id": "1", "product": "tshirt", "size": "L", "index": 2 }
{ "_id": "4", "product": "trousers", "size": "S", "index": 0 }
{ "_id": "5", "product": "sweater", "size": "M", "index": 0 }
{ "_id": "5", "product": "sweater", "size": "L", "index": 1 }
复制代码

保留字段为空的文档

注意到咱们的集合中有两行特殊的空值数据:

...
{ "_id": "2", "product": "pants", "size": [] }
{ "_id": "3", "product": "socks", "size": null }
...
复制代码

若是想要在输出中保留 size 为空数组、null,或者 size 字段不存在的文档,可使用 preserveNullAndEmptyArrays 参数

db.collection('products')
  .aggregate()
  .unwind({
      path: '$size',
      preserveNullAndEmptyArrays: true
  })
  .end()
复制代码

输出以下:

{ "_id": "1", "product": "tshirt", "size": "S" }
{ "_id": "1", "product": "tshirt", "size": "M" }
{ "_id": "1", "product": "tshirt", "size": "L" }
{ "_id": "2", "product": "pants", "size": null }
{ "_id": "3", "product": "socks", "size": null }
{ "_id": "4", "product": "trousers", "size": "S" }
{ "_id": "5", "product": "sweater", "size": "M" }
{ "_id": "5", "product": "sweater", "size": "L" }
复制代码

end

标志聚合操做定义完成,发起实际聚合操做

返回值

Promise.

属性 类型 说明
list Array. 聚合结果列表

示例代码

const $ = db.command.aggregate
db.collection('books').aggregate()
  .group({
    // 按 category 字段分组
    _id: '$category',
    // 让输出的每组记录有一个 avgSales 字段,其值是组内全部记录的 sales 字段的平均值
    avgSales: $.avg('$sales')
  })
  .end()
  .then(res => console.log(res))
  .catch(err => console.error(err))
复制代码
相关文章
相关标签/搜索