1. 程式人生 > 資料庫 >MongoDB Aggregation(聚合)

MongoDB Aggregation(聚合)

聚合操作處理資料記錄並返回計算結果。將來自多個文件的操作組值聚合在一起,並可以對分組的資料執行各種操作以返回單個結果。MongoDB提供了三種執行聚合的方法:聚合管道、map-reduce函式和單一用途的聚合方法。

一、聚合管道

聚合管道是基於資料處理管道概念建模的資料聚合框架。文件進入一個多階段的管道,該管道將文件轉換為聚合的結果。例如:

例子:

db.orders.aggregate([
   { $match: { status: "A" } },{ $group: { _id: "$cust_id",total: { $sum: "$amount" } } }
])

第一階段:$match階段根據狀態欄位篩選文件,並將狀態等於“A”的文件傳遞到下一階段。

第二階段:$group階段根據cust_id欄位對文件進行分組,以計算每個惟一的cust_id的金額總和。

1.管道

MongoDB聚合管道由階段組成。每個階段都在文件通過管道時對其進行轉換。流水線階段不需要為每個輸入文件生成一個輸出文件;例如,某些階段可能生成新文件或過濾掉文件。

除了$out、$merge和$geoNear階段外,管道階段可以在管道中出現多次。有關所有可用階段的列表,請參見聚合管道階段。

MongoDB在mongo shell中提供了db.collection.aggregate()方法,並提供了執行聚合管道的聚合命令。

例如,使用聚合管道時,請考慮使用使用者首選項資料進行聚合和使用郵政編碼資料集進行聚合。

從MongoDB 4.2開始,你可以使用聚合管道進行更新:

Command mongo Shell Methods
findAndModify

db.collection.findOneAndUpdate()

db.collection.findAndModify()

update

db.collection.updateOne()

db.collection.updateMany()

db.collection.update()

Bulk.find.update()

Bulk.find.updateOne()

Bulk.find.upsert()

2. 管道表示式

一些管道階段採用管道表示式作為運算元。管道表示式指定要應用到輸入文件的轉換。表示式具有文件結構,可以包含其他表示式。

管道表示式只能對管道中的當前文件進行操作,不能引用來自其他文件的資料:表示式操作提供文件在記憶體中的轉換。
通常,表示式是無狀態的,只有在聚合過程看到一個例外情況時才進行計算:累加器表示式。

在$group階段中使用的累加器在文件通過管道時維護它們的狀態(例如總計、最大值、最小值和相關資料)。
版本3.2的變化:$project階段提供了一些累加器;但是,在$project階段使用時,累加器不會跨文件維護它們的狀態。
有關表示式的更多資訊,請參見表示式。

3.聚合管道的行為

在MongoDB中,聚合命令操作單個集合,邏輯上將整個集合傳遞到聚合管道。為了優化操作,儘可能使用以下策略來避免掃描整個集合。

3.1 管道運營商及指標

MongoDB的查詢規劃器分析聚合管道,以確定是否可以使用索引來提高管道效能。例如,下面的管道階段可以利用索引:

請注意:
下面的管道階段並不代表可以使用索引的所有階段的完整列表。

$match
如果索引出現在管道的開頭,則$match階段可以使用索引來篩選文件。
$sort
$sort階段可以使用索引,只要索引之前沒有$project、$unwind或$group階段。
$group
$group階段有時可以使用索引來查詢每個組中的第一個文件,前提是滿足以下所有條件:

  • $group階段之前是一個$sort階段,用於對欄位進行分組,
  • 在分組的欄位上有一個與排序順序和匹配的索引
  • $group階段中惟一使用的累加器是$first。

$geoNear

$geoNear管道操作符利用地理空間索引。在使用$geoNear時,$geoNear管道操作必須作為聚合管道中的第一階段出現。

3.2版本中的變化:從MongoDB 3.2開始,索引可以覆蓋聚合管道。在MongoDB 2.6和3.0中,索引不能覆蓋聚合管道,因為即使管道使用索引,聚合仍然需要訪問實際的文件。

3.2 早期的過濾

如果聚合操作只需要集合中的一部分資料,那麼可以使用$match、$limit和$skip階段來限制在管道開始時輸入的文件。當放置在管道的開頭時,$match操作使用合適的索引只掃描集合中匹配的文件。

在管道的開始處放置一個$match管道階段,然後放置一個$sort階段,這在邏輯上等同於一個帶有sort的查詢,並且可以使用索引。如果可能,在管道的開頭放置$match操作符。

4. 注意事項

4.1 分片集合

聚合管道支援對切分集合的操作。參見聚合管道和切分集合。

4.2 聚合vs使用對映-規約模式

聚合管道提供了map-reduce之外的另一種選擇,並且可能是不需要map-reduce複雜性的聚合任務的首選解決方案。

4.3 侷限性

聚合管道對值型別和結果大小有一些限制。有關聚合管道的限制和限制的詳細資訊,請參閱聚合管道限制。

4.4 流水線優化

聚合管道有一個內部優化階段,為某些操作符序列提供改進的效能。有關詳細資訊,請參見聚合管道優化。

二、聚合管道優化

聚合管道操作有一個優化階段,該階段嘗試重新塑造管道以提高效能。

要檢視優化器如何轉換特定的聚合管道,請在db. collections .aggregate()方法中包含explain選項。

1. 投影優化

聚合管道可以確定是否只需要文件中的一個欄位子集就可以獲得結果。如果是這樣,管道將只使用那些必需的欄位,從而減少通過管道的資料量。

2.管道序列優化

($project或$unset或$addFields或$set) + $match序列優化

對於包含一個投影階段($project或$unset或$addFields或$set)和一個$match階段的聚合管道,MongoDB將$match階段中不需要在投影階段計算值的任何過濾器移動到一個新的$match階段,然後再進行投影。

如果聚合管道包含多個投影和/或$match階段,MongoDB將對每個$match階段執行此優化,在篩選器不依賴的所有投影階段之前移動每個$match篩選器。

考慮以下幾個階段的管道:

{ $addFields: {
    maxTime: { $max: "$times" },minTime: { $min: "$times" }
} },{ $project: {
    _id: 1,name: 1,times: 1,maxTime: 1,minTime: 1,avgTime: { $avg: ["$maxTime","$minTime"] }
} },{ $match: {

    name: "Joe Schmoe",maxTime: { $lt: 20 },minTime: { $gt: 5 },avgTime: { $gt: 7 }

} }

優化器將$match階段分解為四個單獨的過濾器,每個過濾器對應$match查詢文件中的每個鍵。然後優化器在儘可能多的投影階段之前移動每個篩選器,根據需要建立新的$match階段。在這個例子中,優化器產生以下優化管道:

{ $match: { name: "Joe Schmoe" } },{ $addFields: {
    maxTime: { $max: "$times" },{ $match: { maxTime: { $lt: 20 },minTime: { $gt: 5 } } },{ $match: { avgTime: { $gt: 7 } } }

$match過濾器{avgTime: {$gt: 7}}依賴於$project階段來計算avgTime欄位。$project階段是此管道中的最後一個投影階段,因此無法移動avgTime上的$match過濾器。

maxTime和minTime欄位在$addFields階段計算,但不依賴於$project階段。優化器為這些欄位上的過濾器建立了一個新的$match階段,並將其置於$project階段之前。

$match篩選器{name: "Joe Schmoe"}不使用在$project或$addFields階段中計算的任何值,因此它在兩個投影階段之前被移動到一個新的$match階段。

請注意:
優化之後,過濾器{name: "Joe Schmoe"}在管道開始處處於$match階段。這還有一個好處,即允許聚合在最初查詢集合時使用name欄位上的索引。有關更多資訊,請參見管道操作符和索引。

$sort + $match序列優化

當您有一個$sort後面跟著$match的序列時,$match在$sort之前移動,以最小化要排序的物件的數量。例如,如果管道由以下幾個階段組成:

{ $sort: { age : -1 } },{ $match: { status: 'A' } }

在優化階段,優化器將序列轉換為以下內容:

{ $match: { status: 'A' } },{ $sort: { age : -1 } }

$redact + $match序列優化

如果可能,當管道中的$redact階段緊跟在$match階段之後時,聚合有時可以在$redact階段之前新增$match階段的一部分。如果新增的$match階段位於管道的開頭,則聚合可以使用索引和查詢集合來限制進入管道的文件數量。有關更多資訊,請參見管道操作符和索引。

例如,如果管道由以下幾個階段組成:

{ $redact: { $cond: { if: { $eq: [ "$level",5 ] },then: "$$PRUNE",else: "$$DESCEND" } } },{ $match: { year: 2014,category: { $ne: "Z" } } }

優化器可以在$redact階段之前新增相同的$match階段:

{ $match: { year: 2014 } },{ $redact: { $cond: { if: { $eq: [ "$level",category: { $ne: "Z" } } }

$project/$unset + $skip序列優化

新版本3.2。
當您有一個$project或$unset後面跟著$skip的序列時,$skip在$project之前移動。例如,如果管道由以下幾個階段組成:

{ $sort: { age : -1 } },{ $project: { status: 1,name: 1 } },{ $skip: 5 }

在優化階段,優化器將序列轉換為以下內容:

{ $sort: { age : -1 } },{ $skip: 5 },name: 1 } }

3. 管道聯合優化

在可能的情況下,優化階段將管道階段合併到其前身。通常,在任何序列重新排序優化之後都會發生合併。

$sort + $limit合併

在4.0版本中進行了更改。
當$sort先於$limit時,如果沒有中間階段修改文件的數量(例如$unwind、$group),那麼優化器可以將$limit合併到$sort中。如果有管道階段在$sort和$limit階段之間更改文件數量,MongoDB不會將$limit合併到$sort中。

例如,如果管道由以下幾個階段組成:

{ $sort : { age : -1 } },{ $project : { age : 1,status : 1,name : 1 } },{ $limit: 5 }

在優化階段,優化器合併序列如下:

{
    "$sort" : {
       "sortKey" : {
          "age" : -1
       },"limit" : NumberLong(5)
    }
},{ "$project" : {
         "age" : 1,"status" : 1,"name" : 1
  }
}

這允許排序操作在進行過程中只維護前n個結果,其中n是指定的限制,MongoDB只需要在記憶體[1]中儲存n個條目。有關更多資訊,請參見$sort操作符和記憶體。

使用$SKIP進行序列優化
如果在$sort和$limit階段之間存在$skip階段,MongoDB將把$limit合併到$sort階段,並將$limit值增加$skip金額。有關示例,請參見$sort + $skip + $limit Sequence。

$limit + $limit合併

當一個$限額緊隨另一個$限額之後時,這兩個階段可以合併為一個$限額,其中限額金額是兩個初始限額中較小的那個。例如,管道包含以下序列:

{ $limit: 100 },{ $limit: 10 }

然後,第二個$limit階段可以合併為第一個$limit階段,併產生一個$limit階段,其中限額金額10是兩個初始限額100和10的最小值。

{ $limit: 10 }

$skip + $skip合併

當一個$skip緊跟著另一個$skip時,這兩個階段可以合併成一個$skip,其中skip金額是兩個初始skip金額的總和。例如,管道包含以下序列:

{ $skip: 5 },{ $skip: 2 }

然後,第二個$skip階段可以合併為第一個$skip階段,併產生一個$skip階段,其中skip amount 7是兩個初始限制5和2的總和。

{ $skip: 7 }

$match + $match合併

當$match緊跟在另一個$match之後時,這兩個階段可以合併為單個$match,將條件與$and組合在一起。例如,管道包含以下序列:

{ $match: { year: 2014 } },{ $match: { status: "A" } }

然後,第二個$match階段可以合併為第一個$match階段,併產生單個$match階段

{ $match: { $and: [ { "year" : 2014 },{ "status" : "A" } ] } }

$lookup + $unwind合併

新版本3.2。
當$unwind緊跟在另一個$lookup之後,並且$unwind操作在$lookup的as欄位上時,優化器可以將$unwind合併到$lookup階段。這避免了建立大型中間文件。

例如,管道包含以下序列:

{
  $lookup: {
    from: "otherCollection",as: "resultingArray",localField: "x",foreignField: "y"
  }
},{ $unwind: "$resultingArray"}

優化器可以將$unwind階段合併為$lookup階段。如果使用explain選項執行聚合,則explain輸出將顯示合併階段:

{
  $lookup: {
    from: "otherCollection",foreignField: "y",unwinding: { preserveNullAndEmptyArrays: false }
  }
}

4. 例子

$sort + $skip + $limit序列

管道包含一個$sort序列,後面跟著一個$skip,後面跟著一個$limit:

{ $sort: { age : -1 } },{ $skip: 10 },{ $limit: 5 }

優化器執行$sort + $limit合併,將序列轉換為以下內容:

{
   "$sort" : {
      "sortKey" : {
         "age" : -1
      },"limit" : NumberLong(15)
   }
},{
   "$skip" : NumberLong(10)
}

MongoDB通過重新排序增加$limit金額。

三、聚合管道極限

使用聚合命令的聚合操作具有以下限制。

1.結果大小限制

版本3.6中的變化:MongoDB 3.6刪除了聚合命令以單個文件的形式返回結果的選項。

聚合命令可以返回遊標,也可以將結果儲存在集合中。當返回遊標或將結果儲存在集合中時,結果集中的每個文件都受BSON文件大小限制,目前為16兆位元組;如果任何單個文件超過了BSON文件大小限制,該命令將產生一個錯誤。該限制只適用於已退回的檔案;在管道處理期間,文件可能超過這個大小。aggregate()方法返回一個遊標。

2. 記憶體限制

流水線階段的RAM限制為100 MiB(100 * 1024 * 1024位元組)。如果一個階段超過這個限制,MongoDB將產生一個錯誤。要允許處理大型資料集,可以在aggregate()方法中設定allowDiskUse選項。allowDiskUse選項允許大多數聚合管道操作將資料寫入臨時檔案。allowDiskUse選項的例外是以下聚合操作;這些操作必須保持在記憶體限制限制:

  • $graphLookup階段
  • $group階段中使用的$addToSet累加器表示式(從版本4.2.3、4.0.14和3.6.17開始)
  • $group階段中使用的$push累加器表示式(從版本4.2.3、4.0.14和3.6.17開始)

如果管道包含其他在aggregate()操作中觀察allowDiskUse: true的階段,則allowDiskUse: true選項對這些其他階段有效。

從MongoDB 4.2開始,分析器日誌訊息和診斷日誌訊息包括一個usedDisk指示器,如果任何聚合階段由於記憶體限制將資料寫入臨時檔案。

四、聚合管道和切分集合

聚合管道支援對切分集合的操作。本節描述特定於聚合管道和切分集合的行為。

1. 效能

在3.2版本中進行了更改。
如果管道從切分鍵上的精確$match開始,則整個管道僅在匹配的切分上執行。在以前,管道會被分割,而合併它的工作必須在主碎片上完成。

對於必須在多個切分上執行的聚合操作,如果這些操作不需要在資料庫的主切分上執行,則這些操作將把結果路由到一個隨機切分以合併結果,以避免對該資料庫的主切分超載。$out階段和$lookup階段需要在資料庫的主碎片上執行。

2. 優化

當將聚合管道分成兩部分時,將管道進行分割,以確保shard執行儘可能多的階段,同時考慮優化。

要檢視如何分割管道,請在db.collection.aggregate()方法中包含explain選項。
優化可能會在不同版本之間發生變化。

五、Aggregation with the Zip Code Data Set

本文件中的示例使用zipcodes集合。這個集合可以在:media.mongodb.org/zips.json中找到。使用mongoimport將這個資料集載入到mongod例項中。

1. 資料模型

郵政編碼收集內的每一份檔案均有下列表格:

{
  "_id": "10280","city": "NEW YORK","state": "NY","pop": 5574,"loc": [
    -74.016323,40.710537
  ]
}
  • _id欄位將郵政編碼儲存為字串。
  • city欄位包含城市名稱。一個城市可以有多個與其相關的郵政編碼,因為城市的不同部分可以有不同的郵政編碼。
  • state欄位包含兩個字母的州縮寫。
  • pop欄位容納人口。
  • loc欄位以經緯度對的形式儲存位置。

1.1 aggregate()方法

下面的所有示例都使用了mongo shell中的aggregate()幫助器。
aggregate()方法使用聚合管道將文件處理為聚合結果。聚合管道由多個階段組成,每個階段在文件通過管道時處理文件。文件依次通過各個階段。

mongo shell中的aggregate()方法為aggregate資料庫命令提供了一個包裝器。有關資料聚合操作的更慣用的介面,請參閱驅動程式的文件。

1.2 返回人口超過1000萬的州

下面的聚合操作返回總人口大於1000萬的所有狀態:

db.zipcodes.aggregate( [
   { $group: { _id: "$state",totalPop: { $sum: "$pop" } } },{ $match: { totalPop: { $gte: 10*1000*1000 } } }
] )

在本例中,聚合管道由$group階段和$match階段組成:

  • $group stage根據狀態欄位對zipcode集合的文件進行分組,為每個狀態計算totalPop欄位,併為每個惟一狀態輸出一個文件。新的每個狀態文件有兩個欄位:_id欄位和totalPop欄位。_id欄位包含狀態值;即按欄位分組。totalPop欄位是一個計算過的欄位,它包含每個狀態的總體。為了計算該值,$group使用$sum操作符為每個狀態新增人口欄位(pop)。

在$group階段之後,準備中的文件如下:

{
  "_id" : "AK","totalPop" : 550043
}
  • $match階段過濾這些分組文件,只輸出totalPop值大於或等於1000萬的文件。$match階段不改變匹配文件,但輸出未修改的匹配文件。

這個聚合操作的等效SQL是:

SELECT state,SUM(pop) AS totalPop
FROM zipcodes
GROUP BY state
HAVING totalPop >= (10*1000*1000)

2. 按州返回平均城市人口

下面的彙總操作返回每個州城市的平均人口:

db.zipcodes.aggregate( [
   { $group: { _id: { state: "$state",city: "$city" },pop: { $sum: "$pop" } } },{ $group: { _id: "$_id.state",avgCityPop: { $avg: "$pop" } } }
] )

在本例中,聚合管道由$group階段和另一個$group階段組成:

  • 第一個$group階段按照城市和州的組合對文件進行分組,使用$sum表示式計算每個組合的人口,並輸出每個城市和州組合的文件。[1]

在這個準備階段之後,檔案類似如下:

{
  "_id" : {
    "state" : "CO","city" : "EDGEWATER"
  },"pop" : 13154
}
  • 第二個$group階段按_id對管道中的文件進行分組。狀態欄位(即_id文件中的狀態欄位),使用$avg表示式計算每個州的平均城市人口(avgCityPop),併為每個州輸出一個文件。

這種聚合操作產生的文件如下:

{
  "_id" : "MN","avgCityPop" : 5335
}

3. 按州返回最大和最小的城市

下面的聚合操作按人口大小返回每個州的城市:

db.zipcodes.aggregate( [
   { $group:
      {
        _id: { state: "$state",pop: { $sum: "$pop" }
      }
   },{ $sort: { pop: 1 } },{ $group:
      {
        _id : "$_id.state",biggestCity:  { $last: "$_id.city" },biggestPop:   { $last: "$pop" },smallestCity: { $first: "$_id.city" },smallestPop:  { $first: "$pop" }
      }
   },// the following $project is optional,and
  // modifies the output format.

  { $project:
    { _id: 0,state: "$_id",biggestCity:  { name: "$biggestCity",pop: "$biggestPop" },smallestCity: { name: "$smallestCity",pop: "$smallestPop" }
    }
  }
] )

在本例中,聚合管道包括$group階段、$sort階段、另一個$group階段和$project階段:

  • 第一個$group stage根據城市和州的組合對文件進行分組,計算每個組合的流行值之和,並輸出每個城市和州組合的文件。

在籌備階段,這些檔案大致如下:

{
  "_id" : {
    "state" : "CO","pop" : 13154
}
  • $sort階段根據pop欄位值(從最小到最大)對管道中的文件進行排序;即通過增加順序。此操作不更改文件。
  • 下一個$group階段按_id對當前已排序的文件進行分組。狀態欄位(即_id文件中的狀態欄位),併為每個狀態輸出一個文件。該階段還為每個狀態計算以下四個欄位。$group操作符使用$last表示式建立最大城市和最大pop欄位,儲存人口最多的城市和人口最多的城市。使用$first表示式,$group操作符建立smallestCity和smallestPop欄位,它們儲存人口最小的城市和該人口。

目前正在擬訂的檔案如下:

{
  "_id" : "WA","biggestCity" : "SEATTLE","biggestPop" : 520096,"smallestCity" : "BENGE","smallestPop" : 2
}
  • 最後的$project階段將_id欄位重新命名為state,並將最大的city、biggestPop、smallestCity和smallestPop移動到biggestCity和smallestCity嵌入文件中。

該聚合操作的輸出文件如下:

{
  "state" : "RI","biggestCity" : {
    "name" : "CRANSTON","pop" : 176404
  },"smallestCity" : {
    "name" : "CLAYVILLE","pop" : 45
  }
}

六、與使用者首選項資料聚合

1.資料模型

假設一個體育俱樂部擁有一個數據庫,其中包含一個使用者集合,該集合跟蹤使用者的加入日期、運動偏好,並將這些資料儲存在類似於以下文件的文件中:

{
  _id : "jane",joined : ISODate("2011-03-02"),likes : ["golf","racquetball"]
}
{
  _id : "joe",joined : ISODate("2012-07-02"),likes : ["tennis","golf","swimming"]
}

2. 對文件進行規範化和排序

下面的操作以大寫字母和字母順序返回使用者名稱。聚合包括使用者集合中所有文件的使用者名稱。您可以這樣做來規範化處理的使用者名稱。

db.users.aggregate(
  [
    { $project : { name:{$toUpper:"$_id"},_id:0 } },{ $sort : { name : 1 } }
  ]
)

使用者集合中的所有文件都要經過流水線,流水線由以下操作組成:

(1)$project操作:

  • 建立一個名為name的新欄位。
  • 使用$toUpper操作符將_id的值轉換為大寫。然後,$project建立一個名為name的新欄位來儲存這個值。
  • 取消id欄位。$project預設情況下會傳遞_id欄位,除非顯式禁止。

(2)$sort操作符根據name欄位對結果排序。

彙總的結果如下:

{
  "name" : "JANE"
},{
  "name" : "JILL"
},{
  "name" : "JOE"
}

3. 返回按加入月排序的使用者名稱

下面的聚合操作將返回按加入月份排序的使用者名稱。這種聚合可以幫助生成會員續訂通知。

db.users.aggregate(
  [
    { $project :
       {
         month_joined : { $month : "$joined" },name : "$_id",_id : 0
       }
    },{ $sort : { month_joined : 1 } }
  ]
)

管道通過以下操作傳遞使用者集合中的所有文件:

(1)$project操作符:

  • 建立兩個新欄位:month_join和name。
  • 從結果中取消id。除非顯式禁止,否則聚合()方法包括_id。

(2)$month操作符將聯接欄位的值轉換為月份的整數表示形式。然後,$project操作符將這些值分配給month_joining欄位。
(3)$sort操作符根據month_joining欄位對結果進行排序。

操作返回類似以下的結果:

{
  "month_joined" : 1,"name" : "ruth"
},{
  "month_joined" : 1,"name" : "harold"
},"name" : "kate"
}
{
  "month_joined" : 2,"name" : "jill"
}

4. 返回每個月的連線總數

下面的操作顯示每年每個月有多少人加入。您可以將這些聚合資料用於招聘和營銷策略。

db.users.aggregate(
  [
    { $project : { month_joined : { $month : "$joined" } } },{ $group : { _id : {month_joined:"$month_joined"},number : { $sum : 1 } } },{ $sort : { "_id.month_joined" : 1 } }
  ]
)

管道通過以下操作傳遞使用者集合中的所有文件:

(1)$project操作符建立一個名為month_join的新欄位。
(2)$month操作符將聯接欄位的值轉換為月份的整數表示形式。然後,$project操作符將這些值分配給month_joining欄位。

(3)$group操作符收集具有給定month_join值的所有文件,並計算該值有多少個文件。具體來說,對於每個惟一的值,$group建立一個新的“每月”文件,其中包含兩個欄位:

  • _id,它包含一個巢狀的文件,其中包含month_join欄位及其值。
  • number,它是一個生成的欄位。對於每個包含給定的month_join值的文件,$sum操作符將該欄位增加1。

(4)$sort操作符根據month_joining欄位的內容對$group建立的文件進行排序。

此聚合操作的結果將類似於以下內容:

{
  "_id" : {
    "month_joined" : 1
  },"number" : 3
},{
  "_id" : {
    "month_joined" : 2
  },"number" : 9
},{
  "_id" : {
    "month_joined" : 3
  },"number" : 5
}

5. 返回五個最常見的“贊”

下面的彙總收集了資料集中最受歡迎的五個活動。這種型別的分析可以幫助規劃和未來的發展。

db.users.aggregate(
  [
    { $unwind : "$likes" },{ $group : { _id : "$likes",{ $sort : { number : -1 } },{ $limit : 5 }
  ]
)

管道從使用者集合中的所有文件開始,通過以下操作傳遞這些文件:

  • $unwind操作符分離like陣列中的每個值,併為陣列中的每個元素建立源文件的新版本。

例子:
給定使用者集合中的以下文件:

{
  _id : "jane","racquetball"]
}

$unwind操作符將建立以下文件:

{
  _id : "jane",likes : "golf"
}
{
  _id : "jane",likes : "racquetball"
}
  • $group操作符為likes欄位收集具有相同值的所有文件,並對每個分組進行計數。有了這些資訊,$group建立了一個帶有兩個欄位的新文件:(1)_id,它包含了like值。(2)number,它是一個生成的欄位。$sum操作符對每個包含給定的like值的文件將該欄位增加1。
  • $sort操作符按數字欄位以相反的順序對這些文件進行排序。
  • $limit操作符只包含前5個結果文件。

彙總的結果如下:

{
  "_id" : "golf","number" : 33
},{
  "_id" : "racquetball","number" : 31
},{
  "_id" : "swimming","number" : 24
},{
  "_id" : "handball","number" : 19
},{
  "_id" : "tennis","number" : 18
}

七、Map-Reduce和Sharded集合

Map-reduce支援對切分集合進行操作,既可以作為輸入,也可以作為輸出。本節描述特定於切分集合的mapReduce行為。

但是,從4.2版開始,MongoDB就不支援使用map-reduce選項來建立新的切分集合,也不支援使用切分選項來進行map-reduce。要輸出到sharded集合,首先建立sharded集合。MongoDB 4.2還反對替換現有的切分集合。

1. 作為輸入的切分集合

當使用sharded collection作為map-reduce操作的輸入時,mongos將自動並行地將map-reduce作業分派給每個shard。不需要特殊選項。mongos將等待所有碎片上的工作完成。

2. 作為輸出的切分集合

如果mapReduce的out欄位有分片值,MongoDB使用_id欄位作為分片鍵對輸出集合進行分片。

請注意:
從4.2版開始,MongoDB就不支援在mapReduce/db.collection.mapReduce中使用切分選項。

輸出到分片集合:

  • 如果輸出集合不存在,則首先建立切分集合。從4.2版開始,MongoDB就不支援使用map-reduce選項來建立新的sharded集合,也不支援使用sharded選項來進行map-reduce。因此,要輸出到sharded集合,首先要建立sharded集合。如果您沒有首先建立切分集合,MongoDB將在_id欄位上建立並切分集合。但是,建議您首先建立切分集合。
  • 從4.2版開始,MongoDB不支援替換現有的切分集合。
  • 從4.0版本開始,如果輸出集合已經存在但沒有分片,則map-reduce失敗。
  • 對於一個新的或空的切分集合,MongoDB使用map-reduce操作第一階段的結果來建立分佈在切分中的初始塊。
  • mongos並行地將一個map-reduce後處理作業分派給每個擁有塊的碎片。在後期處理期間,每個切分將從其他切分中為自己的塊提取結果,執行最後的reduce/finalize,並在本地將結果寫入輸出集合。

請注意:
在後來的map-reduce作業中,MongoDB根據需要分割塊。
為了避免併發性問題,在後處理期間會自動阻止輸出集合的塊的平衡。

八、使用對映-規約模式併發

map-reduce操作由許多工組成,包括對輸入集合的讀取、map函式的執行、reduce函式的執行、在處理期間對臨時集合的寫入以及對輸出集合的寫入。

在操作過程中,map-reduce採取以下鎖:

  • 讀階段採用讀鎖。它每生成100個文件。
  • 臨時集合中的插入對單個寫操作使用寫鎖。
  • 如果輸出集合不存在,則輸出集合的建立將使用寫鎖。
  • 如果存在輸出集合,則輸出操作(即合併、替換、減少)採取寫鎖。這個寫鎖是全域性的,並阻塞mongod例項上的所有操作。

請注意:
後處理期間的最終寫鎖使結果自動顯示。但是,合併和減少輸出操作可能需要幾分鐘的時間。對於merge和reduce,非原子標記是可用的,它在寫入每個輸出文件之間釋放鎖。從MongoDB 4.2開始,顯式地設定nonAtomic: false是不贊成的。有關更多資訊,請參見db.collection.mapReduce()引用。

九、使用對映-規約模式的例子

在mongo shell中,db.collection.mapReduce()方法是mapReduce命令的包裝器。下面的例子使用了db.collection.mapReduce()方法:
考慮以下對包含以下原型文件的集合訂單的對映-簡化操作:

{
     _id: ObjectId("50a8240b927d5d8b5891743c"),cust_id: "abc123",ord_date: new Date("Oct 04,2012"),status: 'A',price: 25,items: [ { sku: "mmm",qty: 5,price: 2.5 },{ sku: "nnn",price: 2.5 } ]
}

1. 返回每個客戶的總價

根據cust_id對訂單集合進行map-reduce操作,並計算每個cust_id的價格總和:

(1)定義map函式來處理每個輸入文件:

  • 在函式中,這指的是map-reduce操作正在處理的文件。
  • 該函式將價格對映到每個文件的cust_id,併發出cust_id和價格對。
var mapFunction1 = function() {
                       emit(this.cust_id,this.price);
                   };

(2)定義相應的減少函式有兩個引數keyCustId和valuesPrices:

  • valuesPrices是一個數組,其元素是map函式發出的price值,並按keyCustId分組。
  • 該函式將valuesPrice陣列簡化為其元素的和。
var reduceFunction1 = function(keyCustId,valuesPrices) {
                          return Array.sum(valuesPrices);
                      };

(3)使用mapFunction1對映函式和reduceFunction1 reduce函式對orders集合中的所有文件執行map-reduce。

db.orders.mapReduce(
                     mapFunction1,reduceFunction1,{ out: "map_reduce_example" }
                   )

該操作將結果輸出到一個名為map_reduce_example的集合。如果map_reduce_example集合已經存在,這個操作將用這個map-reduce操作的結果來替換內容:

2. 計算訂單和總數量,每項平均數量

在本例中,您將對所有ord_date值大於01/01/2012的文件的orders集合執行對映縮減操作。按專案分組。sku欄位,並計算每個sku的訂單數量和訂單總量。通過計算每個sku值的訂單平均數量,得出以下結論:

(1)定義map函式來處理每個輸入文件:

  • 在函式中,這指的是map-reduce操作正在處理的文件。
  • 對於每個專案,該函式將sku與一個新物件值相關聯,該物件值包含訂單的計數1和專案數量,併發出sku和值對。
var mapFunction2 = function() {
                       for (var idx = 0; idx < this.items.length; idx++) {
                           var key = this.items[idx].sku;
                           var value = {
                                         count: 1,qty: this.items[idx].qty
                                       };
                           emit(key,value);
                       }
                    };

(2)定義相應的reduce函式有兩個引數keySKU和countObjVals:

  • countObjVals是一個數組,其元素是對映到由map函式傳遞給減速器函式的分組keySKU值的物件。
  • 該函式將countObjVals陣列簡化為一個包含count和qty欄位的reducedValue物件。
  • 在reducedVal中,count欄位包含來自單個數組元素的計數字段的和,而qty欄位包含來自單個數組元素的qty欄位的和。
var reduceFunction2 = function(keySKU,countObjVals) {
                     reducedVal = { count: 0,qty: 0 };

                     for (var idx = 0; idx < countObjVals.length; idx++) {
                         reducedVal.count += countObjVals[idx].count;
                         reducedVal.qty += countObjVals[idx].qty;
                     }

                     return reducedVal;
                  };

(3)定義一個帶有兩個引數key和reducedVal的finalize函式。該函式修改reducedVal物件,新增一個名為avg的計算欄位,並返回修改後的物件:

var finalizeFunction2 = function (key,reducedVal) {

                       reducedVal.avg = reducedVal.qty/reducedVal.count;

                       return reducedVal;

                    };

(4)使用mapFunction2、reduceFunction2和finalizeFunction2函式對orders集合執行map-reduce操作。

db.orders.mapReduce( mapFunction2,reduceFunction2,{
                       out: { merge: "map_reduce_example" },query: { ord_date:
                                  { $gt: new Date('01/01/2012') }
                              },finalize: finalizeFunction2
                     }
                   )

該操作使用query欄位只選擇那些ord_date大於new Date(01/01/2012)的文件。然後將結果輸出到集合map_reduce_example。如果map_reduce_example集合已經存在,那麼該操作將把現有的內容與這個map-reduce操作的結果合併起來。

八、執行增量使用對映-規約模式

Map-reduce操作可以處理複雜的聚合任務。要執行map-reduce操作,MongoDB提供mapReduce命令,在mongo shell中,提供db.collection.mapReduce()包裝器方法。

如果map-reduce資料集不斷增長,您可能希望每次對整個資料集執行遞增的map-reduce操作,而不是執行map-reduce操作。

要執行增量對映-減少:

  1. 在當前集合上執行一個map-reduce作業,並將結果輸出到一個單獨的集合。
  2. 當你有更多的資料要處理,執行後續的map-reduce作業:
  • 指定只匹配新文件的條件的查詢引數。
  • 指定reduce操作的out引數,該操作將新結果合併到現有的輸出集合中。

考慮下面的示例,在這個示例中,您計劃在每天結束時對一個sessions集合執行一個map-reduce操作。

1. 資料設定

sessions集合包含每天記錄使用者會話的文件,例如:

db.sessions.save( { userid: "a",ts: ISODate('2011-11-03 14:17:00'),length: 95 } );
db.sessions.save( { userid: "b",ts: ISODate('2011-11-03 14:23:00'),length: 110 } );
db.sessions.save( { userid: "c",ts: ISODate('2011-11-03 15:02:00'),length: 120 } );
db.sessions.save( { userid: "d",ts: ISODate('2011-11-03 16:45:00'),length: 45 } );

db.sessions.save( { userid: "a",ts: ISODate('2011-11-04 11:05:00'),length: 105 } );
db.sessions.save( { userid: "b",ts: ISODate('2011-11-04 13:14:00'),length: 120 } );
db.sessions.save( { userid: "c",ts: ISODate('2011-11-04 17:00:00'),length: 130 } );
db.sessions.save( { userid: "d",ts: ISODate('2011-11-04 15:37:00'),length: 65 } );

2. 當前集合的初始對映縮減

按如下步驟執行第一個map-reduce操作:

(1)定義將userid對映到包含userid、total_time、count和avg_time欄位的物件的對映函式:

var mapFunction = function() {
                      var key = this.userid;
                      var value = {
                                    userid: this.userid,total_time: this.length,count: 1,avg_time: 0
                                   };

                      emit( key,value );
                  };

(2)定義相應的reduce函式,使用兩個引數鍵和值來計算總時間和計數。鍵對應於userid,值是一個數組,其元素對應於對映到mapFunction中的userid的單個物件。

var reduceFunction = function(key,values) {

                        var reducedObject = {
                                              userid: key,total_time: 0,count:0,avg_time:0
                                            };

                        values.forEach( function(value) {
                                              reducedObject.total_time += value.total_time;
                                              reducedObject.count += value.count;
                                        }
                                      );
                        return reducedObject;
                     };

(3)使用兩個引數key和reducedValue定義finalize函式。該函式修改reducedValue文件以新增另一個欄位平均值並返回修改後的文件。

var finalizeFunction = function (key,reducedValue) {

                          if (reducedValue.count > 0)
                              reducedValue.avg_time = reducedValue.total_time / reducedValue.count;

                          return reducedValue;
                       };

(4) 使用mapFunction、reduceFunction和finalizeFunction函式對會話集合執行map-reduce。將結果輸出到集合session_stat。如果session_stat集合已經存在,則操作將替換內容:

db.sessions.mapReduce( mapFunction,reduceFunction,{
                         out: "session_stat",finalize: finalizeFunction
                       }
                     )

3. 後續的增量使用對映-規約模式

稍後,隨著sessions集合的增長,您可以執行額外的map-reduce操作。例如,向會話集合新增新文件:

db.sessions.save( { userid: "a",ts: ISODate('2011-11-05 14:17:00'),length: 100 } );
db.sessions.save( { userid: "b",ts: ISODate('2011-11-05 14:23:00'),length: 115 } );
db.sessions.save( { userid: "c",ts: ISODate('2011-11-05 15:02:00'),length: 125 } );
db.sessions.save( { userid: "d",ts: ISODate('2011-11-05 16:45:00'),length: 55 } );

在一天結束時,對sessions集合執行增量的map-reduce,但是使用query欄位只選擇新文件。將結果輸出到集合session_stat,但是使用增量對映-reduce的結果減少內容:

db.sessions.mapReduce( mapFunction,{
                         query: { ts: { $gt: ISODate('2011-11-05 00:00:00') } },out: { reduce: "session_stat" },finalize: finalizeFunction
                       }
                     );

九、排除對映函式的故障

map函式是一個JavaScript函式,它將值與鍵關聯或“對映”,並在map-reduce操作期間發出鍵和值對。

請注意:
從4.2.1版開始,MongoDB就不支援在map、reduce和finalize函式中使用帶有作用域(即BSON型別15)的JavaScript。要確定變數的作用域,請使用作用域引數。

要驗證map函式發出的鍵和值對,請編寫自己的emit函式。
考慮一個包含以下原型文件的集合訂單:

{
     _id: ObjectId("50a8240b927d5d8b5891743c"),price: 250,price: 2.5 } ]
}

(1)定義對映函式,將價格對映到每個文件的cust_id,併發出cust_id和價格對:

var map = function() {
    emit(this.cust_id,this.price);
};

(2)定義emit函式來列印key和value:

var emit = function(key,value) {
    print("emit");
    print("key: " + key + "  value: " + tojson(value));
}

(3)使用orders集合中的單個文件呼叫map函式:

var myDoc = db.orders.findOne( { _id: ObjectId("50a8240b927d5d8b5891743c") } );
map.apply(myDoc);

(4)驗證鍵和值對是否如您所期望的那樣。

emit
key: abc123 value:250

(5)使用來自orders集合的多個文件呼叫map函式:

var myCursor = db.orders.find( { cust_id: "abc123" } );

while (myCursor.hasNext()) {
    var doc = myCursor.next();
    print ("document _id= " + tojson(doc._id));
    map.apply(doc);
    print();
}

(6)驗證鍵和值對是否如預期的那樣。

十、對Reduce函式進行故障排除

reduce函式是一個JavaScript函式,它將在map-reduce操作期間與特定鍵關聯的所有值“簡化”為一個單一物件。reduce功能必須滿足各種需求。本教程幫助驗證reduce函式是否滿足以下條件:

  • reduce函式必須返回一個物件,該物件的型別必須與map函式發出的值的型別相同。
  • valuesArray中元素的順序不應該影響reduce函式的輸出。
  • reduce函式必須是冪等的。

有關reduce函式的所有要求的列表,請參見mapReduce或mongo shell幫助器方法db.collection.mapReduce()。

請注意:
從4.2.1版開始,MongoDB就不支援在map、reduce和finalize函式中使用帶有作用域(即BSON型別15)的JavaScript。要確定變數的作用域,請使用作用域引數。

1. 確認輸出型別

您可以測試reduce函式是否返回與map函式發出的值型別相同的值。

(1)定義一個reduceFunction1函式,它接受引數keyCustId和valuesPrices。valuesPrices是一個整數陣列:

var reduceFunction1 = function(keyCustId,valuesPrices) {
                          return Array.sum(valuesPrices);
                      };

(2)定義一個整數陣列樣本:

var myTestValues = [ 5,5,10 ];

(3)使用myTestValues呼叫reduceFunction1:

reduceFunction1('myKey',myTestValues);

(4)驗證reduceFunction1返回的是一個整數:

20

(5)定義一個reduceFunction2函式,它接受引數keySKU和valuesCountObjects。valuesCountObjects是一個包含兩個欄位count和qty的文件陣列:

var reduceFunction2 = function(keySKU,valuesCountObjects) {
                          reducedValue = { count: 0,qty: 0 };

                          for (var idx = 0; idx < valuesCountObjects.length; idx++) {
                              reducedValue.count += valuesCountObjects[idx].count;
                              reducedValue.qty += valuesCountObjects[idx].qty;
                          }

                          return reducedValue;
                      };

(6)定義一個文件樣本陣列:

var myTestObjects = [
                      { count: 1,qty: 5 },{ count: 2,qty: 10 },{ count: 3,qty: 15 }
                    ];

(7)使用myTestObjects呼叫reduceFunction2:

reduceFunction2('myKey',myTestObjects);

(8)驗證reduceFunction2返回的文件是否包含count和qty欄位:

{ "count" : 6,"qty" : 30 }

2. 確保對對映值的順序不敏感

reduce函式的引數是一個鍵和一個值陣列。您可以測試reduce函式的結果是否依賴於值陣列中元素的順序。

(1)定義一個示例values1陣列和一個示例values2陣列,它們只在陣列元素的順序上不同:

var values1 = [
                { count: 1,qty: 15 }
              ];

var values2 = [
                { count: 3,qty: 15 },{ count: 1,qty: 10 }
              ];

(2)定義一個reduceFunction2函式,它接受引數keySKU和valuesCountObjects。valuesCountObjects是一個包含兩個欄位count和qty的文件陣列:

var reduceFunction2 = function(keySKU,qty: 0 };

                          for (var idx = 0; idx < valuesCountObjects.length; idx++) {
                              reducedValue.count += valuesCountObjects[idx].count;
                              reducedValue.qty += valuesCountObjects[idx].qty;
                          }

                          return reducedValue;
                      };

(3)首先使用values1呼叫reduceFunction2,然後使用values2:

reduceFunction2('myKey',values1);
reduceFunction2('myKey',values2);

(4)驗證reduceFunction2返回相同的結果:

{ "count" : 6,"qty" : 30 }

3. 確保降低函式冪等性

因為map-reduce操作可以對同一個鍵多次呼叫reduce,而不會對工作集中鍵的單個例項呼叫reduce,所以reduce函式必須返回與map函式發出的值相同型別的值。您可以測試reduce函式處理“簡化”的值,而不影響最終的值。

(1)定義一個reduceFunction2函式,它接受引數keySKU和valuesCountObjects。valuesCountObjects是一個包含兩個欄位count和qty的文件陣列:

var reduceFunction2 = function(keySKU,qty: 0 };

                          for (var idx = 0; idx < valuesCountObjects.length; idx++) {
                              reducedValue.count += valuesCountObjects[idx].count;
                              reducedValue.qty += valuesCountObjects[idx].qty;
                          }

                          return reducedValue;
                      };

(2)定義一個樣本金鑰:

var myKey = 'myKey';

(3)定義一個示例valuesIdempotent陣列,該陣列包含一個呼叫reduceFunction2函式的元素:

var valuesIdempotent = [
                         { count: 1,reduceFunction2(myKey,[ { count:3,qty: 15 } ] )
                       ];

(4)定義一個組合傳遞給reduceFunction2的值的示例values1陣列:

var values1 = [
                { count: 1,qty: 15 }
              ];

(5)首先使用myKey和valuesIdempotent呼叫reduceFunction2,然後使用myKey和values1:

reduceFunction2(myKey,valuesIdempotent);
reduceFunction2(myKey,values1);

(6)驗證reduceFunction2返回相同的結果:

{ "count" : 6,"qty" : 30 }

十一、聚合管道快速引用

請注意:
有關特定運算子的詳細資訊,包括語法和示例,請單擊特定運算子進入其參考頁面。

1. Stages

1.1 Stages (db.collection.aggregate)

 db.collection.aggregate方法,流水線階段出現在一個數組中。文件依次通過各個階段。除了$out、$merge和$geoNear階段外,其他階段都可以在管道中多次出現。

db.collection.aggregate( [ { <stage> },... ] )
Stage Description
$addFields 向文件新增新欄位。與$project類似,$addFields會對流中的每個文件進行整形;具體來說,通過向輸出文件新增新欄位,這些輸出文件包含來自輸入文件的現有欄位和新新增的欄位。
$set是$addFields的別名。
$bucket 根據指定的表示式和桶邊界,將傳入的文件分類到稱為桶的組中。
$bucketAuto 根據指定的表示式將傳入的文件分類到特定數量的組(稱為bucket)中。Bucket邊界將自動確定,以便將文件平均分配到指定數量的Bucket中。
$collStats 返回關於集合或檢視的統計資訊。
$count 返回聚合管道此階段的文件數量的計數。
$facet 在同一輸入文件集的單個階段內處理多個聚合管道。支援建立能夠在單個階段跨多個維度或方面描述資料的多面聚合。
$geoNear 根據與地理空間點的接近程度返回有序的文件流。為地理空間資料合併了$match、$sort和$limit功能。輸出文件包含一個額外的距離欄位,並且可以包含一個位置識別符號欄位。
$graphLookup 對集合執行遞迴搜尋。在每個輸出文件中新增一個新的陣列欄位,該欄位包含對該文件的遞迴搜尋的遍歷結果。
$group 按指定的識別符號表示式對輸入文件進行分組,並將累加器表示式(如果指定的話)應用於每個組。使用所有輸入文件,併為每個不同的組輸出一個文件。輸出文件只包含識別符號欄位,如果指定,還包含累計欄位。
$indexStats 返回關於集合中每個索引的使用情況的統計資訊。
$limit 將未修改的前n個文件傳遞到指定限制為n的管道。對於每個輸入文件,輸出一個文件(前n個文件)或零文件(前n個文件之後)。
$listSessions 列出已啟用足夠長的時間以傳播到系統的所有會話。會話集合。
$lookup 對同一資料庫中的另一個集合執行左外連線,以便從“已連線”集合中篩選文件進行處理。
$match 篩選文件流,只允許將匹配的文件未經修改地傳遞到下一個管道階段。$match使用標準的MongoDB查詢。對於每個輸入文件,輸出一個文件(匹配)或零文件(不匹配)。
$merge

將聚合管道的結果文件寫入集合。該階段可以將結果合併到輸出集合中(插入新文件、合併文件、替換文件、保留現有文件、操作失敗、使用自定義更新管道處理文件)。要使用$merge階段,它必須是管道中的最後一個階段。
新版本4.2。

$out 將聚合管道的結果文件寫入集合。要使用$out階段,它必須是管道中的最後一個階段。
$planCacheStats 返回集合的計劃快取資訊。
$project 對流中的每個文件進行整形,例如新增新欄位或刪除現有欄位。對於每個輸入文件,輸出一個文件。
請參閱$unset以刪除現有欄位。
$redact 根據儲存在文件本身的資訊限制每個文件的內容,從而重新構造流中的每個文件。合併了$project和$match的功能。可用於實現欄位級編校。對於每個輸入文件,輸出一個或零一個文件。
$replaceRoot

用指定的嵌入文件替換文件。該操作替換輸入文件中的所有現有欄位,包括_id欄位。指定嵌入在輸入文件中的文件,將嵌入的文件提升到頂層。
$replaceWith是$replaceRoot階段的別名。

$replaceWith 用指定的嵌入文件替換文件。該操作替換輸入文件中的所有現有欄位,包括_id欄位。指定嵌入在輸入文件中的文件,將嵌入的文件提升到頂層。
$replaceWith是$replaceRoot階段的別名。
$sample 從其輸入中隨機選擇指定數量的文件。
$set 向文件新增新欄位。與$project類似,$set對流中的每個文件進行整形;具體來說,通過向輸出文件新增新欄位,這些輸出文件包含來自輸入文件的現有欄位和新新增的欄位。
$set是$addFields階段的別名。
$skip 跳過前n個文件,其中n是指定的跳過號,並將未修改的其餘文件傳遞給管道。對於每個輸入文件,輸出零個文件(對於前n個文件)或一個文件(如果在前n個文件之後)。
$sort 通過指定的排序鍵對文件流重新排序。只有順序改變了;這些檔案沒有修改。對於每個輸入文件,輸出一個文件。
$sortByCount 根據指定表示式的值對傳入文件進行分組,然後計算每個不同組中的文件數。
$unset 從文件中刪除/排除欄位。
$unset是$project stage的別名,用於刪除欄位。
$unwind 從輸入文件解構一個數組欄位,為每個元素輸出一個文件。每個輸出文件用一個元素值替換陣列。對於每個輸入文件,輸出n個文件,其中n是陣列元素的數量,對於空陣列可以是0。

1.2 Stages (db.aggregate)

從3.6版開始,MongoDB也提供了db.aggregate方法:

db.aggregate( [ { <stage> },... ] )

以下階段使用的是db.aggregate()方法,而不是db.collection.aggregate()方法。

Stage Description
$currentOp 返回關於MongoDB部署的活動和/或休眠操作的資訊。
$listLocalSessions 列出當前連線的mongos或mongod例項上最近使用的所有活動會話。這些會話可能還沒有傳播到系統。會話集合。

1.3 可用於更新的階段

從MongoDB 4.2開始,你可以使用聚合管道進行更新:

 

findAndModify

db.collection.findOneAndUpdate()

db.collection.findAndModify()

update

db.collection.updateOne()

db.collection.updateMany()

db.collection.update()

Bulk.find.update()

Bulk.find.updateOne()

Bulk.find.upsert()

對於更新,管道可以包括以下幾個階段:

  • $addFields及其別名$set
  • $project及其別名$unset
  • $replaceRoot及其別名$replaceWith。

2. 表示式

表示式可以包括欄位路徑、文字、系統變數、表示式物件和表示式操作符。表示式可以巢狀。

2.1 Field Paths

聚合表示式使用欄位路徑訪問輸入文件中的欄位。要指定欄位路徑,請在欄位名或點欄位名(如果欄位在嵌入的文件中)前面加上美元符號$。例如,“$user”指定使用者欄位的欄位路徑,或者“$user.name”指定“user.name”欄位的欄位路徑。

“$<欄位>”等於“$$CURRENT”。<field>"其中,當前是一個系統變數,預設為當前物件的根,除非在特定階段中另有說明。

2.2 聚合變數

MongoDB為表示式提供了各種聚合系統變數。要訪問變數,在變數名前面加上$$。例如:

Variable Access via $$ Brief Description
NOW $$NOW 返回當前日期時間值,該值在部署的所有成員中都是相同的,在整個聚合管道中保持不變。(4.2 +)
CLUSTER_TIME $$CLUSTER_TIME 返回當前時間戳值,該值在部署的所有成員之間是相同的,在整個聚合管道中保持不變。僅用於複製集和分片叢集。(4.2 +)
ROOT $$ROOT 引用根文件,即頂級文件。
CURRENT $$CURRENT 引用欄位路徑的開始,預設情況下是根路徑,但可以更改。
REMOVE $$REMOVE 允許欄位的條件排除。(3.6 +)
DESCEND $$DESCEND 一個$redact表示式允許的結果之一。
PRUNE $$PRUNE 一個$redact表示式允許的結果之一。
KEEP $$KEEP 一個$redact表示式允許的結果之一。

2.3 Literals

文字可以是任何型別。但是,MongoDB解析以美元符號$作為欄位路徑開頭的字串,解析表示式物件中的數字/布林值作為投影標誌。要避免解析文字,可以使用$literal表示式。

2.4 表示式物件

表示式物件有以下形式:

{ <field1>: <expression1>,... }

如果表示式是數值型或布林型文字,MongoDB將文字作為投影標誌(例如1或true來包含欄位),僅在$project階段有效。為了避免將數字或布林文字作為投影標誌,可以使用$literal表示式來包裝數字或布林文字。

3. 操作符表示式

操作符表示式類似於接受引數的函式。通常,這些表示式採用引數陣列,形式如下:

{ <operator>: [ <argument1>,<argument2> ... ] }

如果操作符接受單個引數,你可以忽略指定引數列表的外部陣列:

{ <operator>: <argument> }

如果引數是文字陣列,為了避免解析歧義,您必須將文字陣列包裝在$literal表示式中,或者保留指定引數列表的外部陣列。

3.1 算術表示式運算子

算術表示式對數字進行數學運算。一些算術表示式也可以支援日期運算。

Name Description
$abs 返回一個數字的絕對值。
$add 新增數字以返回總和,或新增數字和日期以返回新日期。如果新增數字和日期,則將這些數字視為毫秒。接受任意數量的引數表示式,但最多隻能解析一個表示式到一個日期。
$ceil 返回大於或等於指定數字的最小整數。
$divide 返回第一個數字除以第二個數字的結果。接受兩個引數表示式。
$exp e的指定指數次方。
$floor 返回小於或等於指定數字的最大整數。
$ln 計算一個數的自然對數。
$log 計算指定基數中數字的日誌。
$log10 計算以10為底的對數。
$mod 返回第一個數字除以第二個數字的餘數。接受兩個引數表示式。
$multiply 將數字相乘以返回產品。接受任意數量的引數表示式。
$pow 將數字提升到指定的指數。
$round 將數字舍入為整數或指定的小數。
$sqrt 計算平方根。
$subtract 返回從第一個值減去第二個值的結果。如果兩個值是數字,則返回差值。如果兩個值是日期,則以毫秒為單位返回差值。如果這兩個值是一個日期和一個以毫秒為單位的數字,則返回結果日期。接受兩個引數表示式。如果這兩個值是日期和數字,那麼首先指定date引數,因為從數字中減去日期沒有意義。
$trunc 將數字截斷為整數或指定的小數位數。

3.2 陣列表示式運算子

$arrayElemAt 返回指定陣列索引處的元素。
$arrayToObject 將鍵值對陣列轉換為文件。
$concatArrays 連線陣列以返回連線後的陣列。
$filter 選擇陣列的一個子集來返回一個數組,該陣列只包含與篩選條件匹配的元素。
$in 返回一個布林值,指示指定的值是否在陣列中。
$indexOfArray 在陣列中搜索指定值的出現,並返回第一次出現的陣列索引。如果沒有找到子字串,則返回-1。
$isArray 確定運算元是否為陣列。返回一個布林值。
$map 對陣列的每個元素應用子表示式,並按順序返回結果值的陣列。接受命名引數。
$objectToArray 將文件轉換為表示鍵-值對的文件陣列。
$range 根據使用者定義的輸入輸出一個包含整數序列的陣列。
$reduce 將表示式應用於陣列中的每個元素並將它們組合成單個值。
$reverseArray 以相反的順序返回元素的陣列。
$size 返回陣列中元素的數目。接受單個表示式作為引數。
$slice 返回陣列的一個子集。
$zip 合併兩個陣列。

3.3 布林表示式運算子

布林表示式將其引數表示式計算為布林值,並返回一個布林值作為結果。

除了假布林值之外,布林表示式的以下計算結果為假:null、0和未定義的值。布林表示式計算所有其他值為真,包括非零數值和陣列。

Name Description
$and 僅當其所有表示式求值為true時才返回true。接受任意數量的引數表示式。
$not 返回與引數表示式相反的布林值。接受單個引數表示式。
$or 當其中一個表示式的計算結果為true時,返回true。接受任意數量的引數表示式。

3.4 比較表示式操作符

除了$cmp返回一個數字之外,比較表示式返回一個布林值。
比較表示式採用兩個引數表示式,使用指定的BSON比較順序比較不同型別的值和型別。

$cmp 如果兩個值相等,則返回0;如果第一個值大於第二個值,則返回1;如果第一個值小於第二個值,則返回-1。
$eq 如果值相等,則返回true。
$gt 如果第一個值大於第二個值,則返回true。
$gte 如果第一個值大於或等於第二個值,則返回true。
$lt 如果第一個值小於第二個值,則返回true。
$lte 如果第一個值小於或等於第二個值,則返回true。
$ne 如果值不相等,則返回true。

3.5 條件表示式運算子

Name Description
$cond 計算一個表示式的三元運算子,根據結果返回另外兩個表示式之一的值。接受有序列表中的三個表示式或三個命名引數。
$ifNull 返回第一個表示式的非空結果,如果第一個表示式導致一個空結果,則返回第二個表示式的結果。空結果包含未定義值或丟失欄位的例項。接受兩個表示式作為引數。第二個表示式的結果可以是null。
$switch 計算一系列大小寫表示式。當它找到一個計算結果為true的表示式時,$switch將執行指定的表示式並跳出控制流。

3.6 日期表達運算子

以下操作符返回日期物件或日期物件的元件:

Name Description
$dateFromParts 給定日期的組成部分構造BSON日期物件。
$dateFromString 將日期/時間字串轉換為日期物件。
$dateToParts 返回包含日期組成部分的文件。
$dateToString 以格式化字串的形式返回日期。
$dayOfMonth 將日期的月日作為1到31之間的數字返回。
$dayOfWeek 返回日期在1(星期日)和7(星期六)之間的數字。
$dayOfYear 返回日期在1和366之間的日期(閏年)。
$hour 將日期的小時作為0到23之間的數字返回。
$isoDayOfWeek 返回ISO 8601格式的工作日編號,範圍從1(週一)到7(週日)。
$isoWeek 返回ISO 8601格式的週數,範圍從1到53。週數從1開始,包含一年的第一個週四的那一週(週一到週日)。
$isoWeekYear 以ISO 8601格式返回年份號。這一年從第一週的星期一開始(ISO 8601),到最後一週的星期日結束(ISO 8601)。
$millisecond 以0到999之間的數字形式返回日期的毫秒數。
$minute 將日期的分鐘作為0到59之間的數字返回。
$month 返回日期的月份作為1(一月)到12(十二月)之間的數字。
$second 以0到60之間的數字(閏秒)返回日期的秒數。
$toDate 將值轉換為日期。
新版本4.0。
$week 返回日期的週數,作為0(一年的第一個星期日之前的部分周)和53(閏年)之間的數字。
$year 以數字形式返回日期的年份(如2014年)

以下算術運算子可以接受日期運算元:

Name Description
$add 新增數字和日期以返回新日期。如果新增數字和日期,則將這些數字視為毫秒。接受任意數量的引數表示式,但最多隻能解析一個表示式到一個日期。
$subtract 新增數字和日期以返回新日期。如果新增數字和日期,則將這些數字視為毫秒。接受任意數量的引數表示式,但最多隻能解析一個表示式到一個日期。

3.7 文字表達式運算子

Name Description
$literal 返回一個不需要解析的值。用於聚合管道可能解釋為表示式的值。例如,對以$開頭的字串使用$literal表示式,以避免將其解析為欄位路徑。

3.8 物件表示式運算子

Name Description
$mergeObjects 將多個文件組合成單個文件。
新版本3.6。
$objectToArray 將文件轉換為表示鍵-值對的文件陣列。
新版本3.6。

3.9 set表示式運算子

Set表示式對陣列執行Set操作,將陣列視為集合。Set表示式忽略每個輸入陣列中的重複項和元素的順序。
如果set操作返回一個set,則該操作過濾掉結果中的重複項,以輸出只包含唯一條目的陣列。輸出陣列中元素的順序是未指定的。

如果集合包含巢狀的陣列元素,則集合表示式不會下降到巢狀的陣列中,而是在頂層計算陣列。

Name Description
$allElementsTrue 如果集合中沒有元素的計算結果為false,則返回true,否則返回false。接受單個引數表示式。
$anyElementTrue 如果集合中的任何元素的值為true,則返回true;否則,返回false。接受單個引數表示式。
$setDifference 返回一個集合,其中的元素出現在第一個集合中,但不在第二個集合中;即執行第二個集合相對於第一個集合的相對補碼。只接受兩個引數表示式。
$setEquals 如果輸入集具有相同的不同元素,則返回true。接受兩個或多個引數表示式。
$setIntersection 返回一個集合,其中的元素出現在所有輸入集中。接受任意數量的引數表示式。
$setIsSubset 如果第一個集合的所有元素都出現在第二個集合中,包括當第一個集合等於第二個集合時,返回true;即不是一個嚴格的子集。只接受兩個引數表示式。
$setUnion 返回包含任何輸入集中出現的元素的集合。

3.10 字串表示式運算子

除了$concat之外,字串表示式只有定義良好的ASCII字元字串行為。

無論使用什麼字元,都可以定義$concat行為。

Name Description
$concat 連線任意數量的字串。
$dateFromString 將日期/時間字串轉換為日期物件。
$dateToString 以格式化字串的形式返回日期。
$indexOfBytes 在字串中搜索子字串的出現,並返回第一次出現的UTF-8位元組索引。如果沒有找到子字串,則返回-1。
$indexOfCP 在字串中搜索子字串的出現,並返回第一次出現的UTF-8程式碼點索引。如果沒有找到子字串,則返回-1
$ltrim 刪除字串開頭的空白或指定字元。
新版本4.0。
$regexFind 將正則表示式(正則表示式)應用於字串並返回第一個匹配的子字串的資訊。
新版本4.2。
$regexFindAll 將正則表示式(正則表示式)應用於字串並返回所有匹配的子字串的資訊。
新版本4.2。
$regexMatch 將正則表示式(regex)應用於字串並返回一個布林值,該布林值指示是否找到匹配項。
新版本4.2。
$rtrim 從字串末尾刪除空白或指定的字元。
新版本4.0。
$split 根據分隔符將字串分成子字串。返回子字串陣列。如果在字串中沒有找到分隔符,則返回包含原始字串的陣列。
$strLenBytes 返回字串中UTF-8編碼的位元組數。
$strLenCP 返回字串中UTF-8程式碼點的數量。
$strcasecmp 執行不區分大小寫的字串比較並返回:如果兩個字串相等,則返回0;如果第一個字串大於第二個字串,則返回1;如果第一個字串小於第二個字串,則返回-1。
$substr 棄用。使用$substrBytes或$substrCP。
$substrBytes 返回字串的子字串。從字串中指定UTF-8位元組索引(從零開始)處的字元開始,然後繼續指定位元組數。
$substrCP 返回字串的子字串。從字串中指定的UTF-8程式碼點(CP)索引(從零開始)處的字元開始,然後繼續指定的程式碼點數量。
$toLower 將字串轉換為小寫形式。接受單個引數表示式。
$toString 將值轉換為字串。
新版本4.0。
$trim 刪除字串開頭和結尾的空白或指定字元。
新版本4.0。
$toUpper 將字串轉換為大寫。接受單個引數表示式。

3.11 文字表示式運算子

Name Description
$meta 訪問文字搜尋元資料。

3.12 三角函式表示式運算子

三角表達式對數字進行三角運算。表示角度的值總是以弧度表示輸入或輸出。使用$degreesToRadians和$radiansToDegrees在度和弧度度量之間進行轉換。

Name Description
$sin 返回以弧度度量的值的正弦值。Returns the sine of a value that is measured in radians.
$cos 返回以弧度度量的值的餘弦值。
$tan 返回以弧度表示的值的正切值。
$asin 返回以弧度為單位的值的反正弦值(arcsin)。
$acos 返回以弧度為單位的值的反餘弦(arccos)。
$atan 返回以弧度為單位的值的反正切(arctan)。
$atan2 返回以弧度表示的y / x的反正切(arctan),其中y和x分別是傳遞給表示式的第一個和第二個值。
$asinh 返回以弧度為單位的值的反雙曲正弦(雙曲反正弦)。
$acosh 返回以弧度為單位的值的反雙曲餘弦(雙曲反餘弦)。
$atanh 返回以弧度為單位的值的反雙曲正切(雙曲反正切)。
$degreesToRadians 將值從度轉換為弧度。
$radiansToDegrees 將值從弧度轉換為角度。

3.13 型別表示式運算子

Name Description
$convert 將值轉換為指定的型別。
新版本4.0。
$toBool 將值轉換為布林值。
新版本4.0。
$toDate 將值轉換為日期。
新版本4.0。
$toDecimal 將值轉換為小數128。
新版本4.0。
$toDouble 將值轉換為雙精度值。
新版本4.0。
$toInt 將值轉換為整數。
新版本4.0。
$toLong 將值轉換為長。
新版本4.0。
$toObjectId 將值轉換為ObjectId。
新版本4.0。
$toString 將值轉換為字串。
新版本4.0。
$type 返回欄位的BSON資料型別。

3.14 Accumulators ($group)

在$group階段可用,累加器是操作符,當文件在管道中進展時,它們維護自己的狀態(例如,總計、最大值、最小值和相關資料)。

在$group階段中用作累加器時,這些操作符將單個表示式作為輸入,對每個輸入文件求值一次,併為共享相同組鍵的文件組維護它們的階段。

Name Description
$addToSet 返回每個組的唯一表達式值陣列。陣列元素的順序未定義。
$avg 返回數值的平均值。忽略了非數字值。
$first 為每個組從第一個文件返回一個值。只有當文件處於已定義的順序時,才定義Order。
$last 為每個組從上一個文件返回一個值。只有當文件處於已定義的順序時,才定義Order。
$max 返回每個組的最高表示式值。
$mergeObjects 返回通過組合每個組的輸入文件建立的文件。
$min 返回每個組的最低表示式值。
$push 返回每個組的表示式值陣列。
$stdDevPop 返回輸入值的總體標準差。
$stdDevSamp 返回輸入值的樣本標準差。
$sum 返回數值的和。忽略了非數字值。

3.15 Accumulators (in Other Stages)

在$group階段可用作累加器的一些操作符也可用作其他階段的累加器,但不能用作累加器。當在這些其他階段使用時,這些操作符不維護它們的狀態,可以接受單個引數或多個引數作為輸入。有關詳細資訊,請參閱特定的操作員頁面。

在3.2版本中進行了更改。
以下累加器操作符在$project、$addFields和$set stage中也可用。

Name Description
$avg 返回每個文件的指定表示式或表示式列表的平均值。忽略了非數字值。
$max 返回每個文件的指定表示式或表示式列表的最大值
$min 返回每個文件的指定表示式或表示式列表的最小值
$stdDevPop 返回輸入值的總體標準差。
$stdDevSamp 返回輸入值的樣本標準差。
$sum 返回數值的和。忽略了非數字值。

3.16 變量表達式運算子

Name Description
$let 定義在子表示式範圍內使用的變數,並返回子表示式的結果。接受命名引數。
接受任意數量的引數表示式。

表示式運算子索引

十五、聚合的命令

請注意:
有關特定運算子的詳細資訊,包括語法和示例,請單擊特定運算子進入其參考頁面。

1. 聚合的命令

Name Description
aggregate 使用聚合框架執行聚合任務,如分組。
count 計算集合或檢視中的文件數量。
distinct 顯示在集合或檢視中為指定鍵找到的不同值。
mapReduce 對大資料集執行map-reduce聚合。

2. Aggregation Methods

Name Description
db.collection.aggregate() 提供對聚合管道的訪問。
db.collection.mapReduce() 對大資料集執行map-reduce聚合。

十六、聚合命令比較

下表簡要概述了MongoDB聚合命令的特性。

aggregate / db.collection.aggregate() mapReduce / db.collection.mapReduce()
Description 為提高聚合任務的效能和可用性而設計。
使用“管道”方法,物件在通過一系列管道操作符(如$group、$match和$sort)時進行轉換。
有關管道操作符的更多資訊,請參見聚合管道操作符。
實現用於處理大型資料集的Map-Reduce聚合。
Key Features 可以根據需要重複管道操作符。
管道操作人員不需要為每個輸入文件生成一個輸出文件。
還可以生成新文件或過濾掉文件。
除了分組操作之外,還可以執行復雜的聚合任務,以及對不斷增長的資料集執行增量聚合。
參見Map-Reduce示例並執行增量對映- reduce。
Flexibility 僅限於聚合管道支援的操作符和表示式。
但是,可以使用$project管道操作符新增計算欄位、建立新的虛擬子物件,並將子欄位提取到頂級結果中。
有關所有可用管道操作符的更多資訊,請參見$project和聚合管道操作符。
自定義對映、reduce和finalize JavaScript函式為聚合邏輯提供了靈活性。
有關函式的詳細資訊和限制,請參見mapReduce。
 

以遊標的形式返回結果。如果管道包含$out階段或$merge階段,則遊標為空。
版本3.6的變化:MongoDB 3.6刪除了不使用遊標選項的聚合命令,除非該命令包含explain選項。除非包含explain選項,否則必須指定遊標選項。

  • 要指示具有預設批處理大小的遊標,請指定遊標:{}。
  • 若要指示具有非預設批處理大小的遊標,請使用遊標:{batchSize: <num>}。
返回各種選項的結果(內聯、新集合、合併、替換、減少)。有關輸出選項的詳細資訊,請參見mapReduce。
Sharding 支援非切分輸入集合和切分輸入集合。 支援非切分輸入集合和切分輸入集合。
More Information

十七、聚合表示式中的變數

聚合表示式既可以使用使用者定義的變數,也可以使用系統變數。
變數可以儲存任何BSON型別的資料。要訪問變數的值,在變數名前面加上雙美元符號($$);即。“$ $ <variable>”。
如果變數引用物件,要訪問物件中的特定欄位,請使用點符號;即。變數“$ $ <variable >.<field>”。

1. 使用者變數

使用者變數名可以包含ascii字元[_a-zA-Z0-9]和任何非ascii字元。
使用者變數名必須以小寫的ascii字母[a-z]或非ascii字元開頭。

2.系統變數

MongoDB提供了以下系統變數:

Variable Description

NOW

返回當前日期時間值的變數。現在,為部署的所有成員返回相同的值,並在聚合管道的所有階段保持相同的值。
新版本4.2。

CLUSTER_TIME

返回當前時間戳值的變數。
CLUSTER_TIME只在複製集和分片叢集上可用。
CLUSTER_TIME為部署的所有成員返回相同的值,並在管道的所有階段保持相同的值。
新版本4.2。

ROOT

引用當前正在聚合管道階段處理的根文件,即頂級文件。

CURRENT

引用聚合管道階段中正在處理的欄位路徑的開始。除非另有說明,否則所有階段都以與ROOT相同的CURRENT開始。
目前是可修改的。但是,由於$<欄位>等於$$CURRENT。<field>, rebinding CURRENT改變了$的含義。

REMOVE

求值為缺失值的變數。允許欄位的條件排除。在$投射中,變數REMOVE的欄位被排除在輸出之外。
有關其用法的示例,請參見有條件排除欄位。
新版本3.6。

DESCEND

一個$redact表示式允許的結果之一。

PRUNE

一個$redact表示式允許的結果之一。

KEEP

一個$redact表示式允許的結果之一。

十八、SQL到聚合對映圖

聚合管道允許MongoDB提供與SQL中的許多常見資料聚合操作相對應的本地聚合功能。

下表概述了常見的SQL聚合術語、函式和概念,以及相應的MongoDB聚合操作符:

SQL Terms,Functions,and Concepts MongoDB Aggregation Operators
WHERE $match
GROUP BY $group
HAVING $match
SELECT $project
ORDER BY $sort
LIMIT $limit
SUM() $sum
COUNT()

$sum

$sortByCount

join $lookup
SELECT INTO NEW_TABLE $out
MERGE INTO TABLE $merge (Available starting in MongoDB 4.2)

1.例項

下表給出了SQL聚合語句和相應的MongoDB語句的快速引用。表中的例子假設了以下條件:

  • SQL示例假設兩個表orders和order_lineitem通過order_lineitem連線。order_id和訂單。id列。
  • MongoDB的例子假設一個收集訂單,其中包含以下原型的文件:
{
  cust_id: "abc123",ord_date: ISODate("2012-11-02T17:04:11.102Z"),price: 50,items: [ { sku: "xxx",qty: 25,price: 1 },{ sku: "yyy",price: 1 } ]
}
SQL Example MongoDB Example Description
SELECT COUNT(*) AS count
FROM orders
db.orders.aggregate( [
   {
     $group: {
        _id: null,count: { $sum: 1 }
     }
   }
] )
統計訂單中的所有記錄
SELECT SUM(price) AS total
FROM orders
db.orders.aggregate( [
   {
     $group: {
        _id: null,total: { $sum: "$price" }
     }
   }
] )
對訂單中的價格欄位求和
SELECT cust_id,SUM(price) AS total
FROM orders
GROUP BY cust_id
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",total: { $sum: "$price" }
     }
   }
] )
對於每個惟一的cust_id,對price欄位求和。
SELECT cust_id,SUM(price) AS total
FROM orders
GROUP BY cust_id
ORDER BY total
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",total: { $sum: "$price" }
     }
   },{ $sort: { total: 1 } }
] )
對於每個惟一的cust_id,對price欄位求和,結果按sum排序。
SELECT cust_id,ord_date,SUM(price) AS total
FROM orders
GROUP BY cust_id,ord_date
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",ord_date: { $dateToString: {
              format: "%Y-%m-%d",date: "$ord_date"
           }}
        },total: { $sum: "$price" }
     }
   }
] )
對於每個惟一的cust_id、ord_date分組,對price欄位求和。不包括日期的時間部分。
SELECT cust_id,count(*)
FROM orders
GROUP BY cust_id
HAVING count(*) > 1
db.orders.aggregate( [
   {
     $group: {
        _id: "$cust_id",count: { $sum: 1 }
     }
   },{ $match: { count: { $gt: 1 } } }
] )
對於包含多條記錄的cust_id,返回cust_id和相應的記錄計數。
SELECT cust_id,ord_date
HAVING total > 250
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",{ $match: { total: { $gt: 250 } } }
] )
對於每個惟一的cust_id、ord_date分組,對price欄位求和,只有當總和大於250時才返回。不包括日期的時間部分。
SELECT cust_id,SUM(price) as total
FROM orders
WHERE status = 'A'
GROUP BY cust_id
db.orders.aggregate( [
   { $match: { status: 'A' } },{
     $group: {
        _id: "$cust_id",total: { $sum: "$price" }
     }
   }
] )
對於每個狀態為A的惟一cust_id,對price欄位求和。
SELECT cust_id,SUM(price) as total
FROM orders
WHERE status = 'A'
GROUP BY cust_id
HAVING total > 250
db.orders.aggregate( [
   { $match: { status: 'A' } },{ $match: { total: { $gt: 250 } } }
] )
對於每個狀態為A的惟一cust_id,對price欄位求和,只在總和大於250時返回。
SELECT cust_id,SUM(li.qty) as qty
FROM orders o,order_lineitem li
WHERE li.order_id = o.id
GROUP BY cust_id
db.orders.aggregate( [
   { $unwind: "$items" },qty: { $sum: "$items.qty" }
     }
   }
] )
對於每個惟一的cust_id,將與訂單相關的行專案qty欄位相加。
SELECT COUNT(*)
FROM (SELECT cust_id,ord_date
      FROM orders
      GROUP BY cust_id,ord_date)
      as DerivedTable
db.orders.aggregate( [
   {
     $group: {
        _id: {
           cust_id: "$cust_id",date: "$ord_date"
           }}
        }
     }
   },{
     $group: {
        _id: null,count: { $sum: 1 }
     }
   }
] )
計算不同的cust_id、ord_date分組的數量。不包括日期的時間部分。