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))
複製代碼
相關文章
相關標籤/搜索