MongoDB—聚合

Aggregation

  • 聚合操作處理數據記錄並返回計算結果
  • 聚合操作將來自多個文檔的值進行分組,對分組的數據進行各種操作並返回單個結果
  • mongodb 提供了三種進行聚合操作的方法:聚合管道、map-reduce函數、single purpose 聚合

Aggregation Pipeline

  • mongodb 的聚合框架是基於數據處理管道的概念建模的,文檔通過一個多階段管道處理,轉換爲聚合結果

  • 聚合管道使用本地操作實現了高效的數據聚合操作,是mongodb首選的數據聚合方法

  • 聚合管道可以對分片集合進行操作

  • 在聚合管道的某些階段,可以使用索引來提高性能。此外,聚合管道有一個內部優化階段

  • 聚合管道由多個階段(stage)組成,每個階段都會對輸入文檔進行處理轉換。管道階段不需要爲每個輸入文檔生成一個輸出文檔,因爲有些階段會生成新的文檔或過濾掉文檔

  • 管道階段可以在管道中出現多次,但 $out、$merge、$geoNear 階段只能出現一次

db.collection.aggregate

  • 計算集合或視圖中數據的聚合結果

  • 遊標

    • 聚合返回的遊標只支持對已計算的遊標進行操作的方法

      https://docs.mongodb.com/v4.2/reference/method/db.collection.aggregate/#cursor-behavior

      Cursors returned from aggregation only supports cursor methods that operate on evaluated cursors (i.e. cursors whose first batch has been retrieved)

      cursor.hasNext()
      cursor.next()
      cursor.toArray()
      cursor.forEach()
      cursor.map()
      cursor.objsLeftInBatch()
      cursor.itcount()
      cursor.pretty()

    • 在 mongo shell 中,如果 aggregate() 方法返回的遊標沒有使用 var 關鍵字分配給一個變量,那麼 mongo shell 將自動迭代遊標20次

  • 會話

    • 從 mongodb 3.6 開始,mongodb驅動和mongo shell 將所有操作與一個服務器會話關聯,處理未確認的寫操作
    • 如果一個會話空閒時間超過30分鐘,則mongodb服務器會將其標記爲過期,並可能在任何時候關閉。mongodb服務器關閉會話的時候會終止任何正在進行的操作,並打開與會話關聯的遊標
    • 在會話內創建的遊標,不能在會話外調用 getMore
    • 在會話外創建的遊標,不能在會話內調用 getMore

格式

db.collection.aggregate(pipeline, options)
  • pipeline

    • 類型:數組
    • 描述:
      • 聚合操作列表
      • 可以接收單個階段而非數組,但是非數組類型無法指定 options 參數
  • options

    • 類型:Document

    • 描述:aggregate() 方法傳遞給 aggregate 命令的額外選項,僅當pipeline爲數組時可用

      • explain

        • 類型:布爾
        • 描述
          • 指定管道處理的返回信息
      • allowDiskUse

        • 類型:布爾

        • 描述:是否允許使用臨時文件

          • true

            聚合操作可以將數據寫入臨時文件,位於 dbPath 目錄中的子目錄_tmp

            但 $graphLookup、$addToSet、$push 階段除外

      • cursor

        • 類型:Document
        • 描述:指定遊標的初始批處理大小
      • maxTimeMS

        • 類型:非負整數(單位 毫秒)
        • 描述
          • 指定處理遊標操作的時間限制
          • 如果沒有指定,則操作不會超時
          • 值0,顯式指定默認的無限制行爲
          • mongodb 使用與 db.killOp() 方法相同的機制終止超時的操作。mongodb 只在一個指定的中斷點終止一個操作
      • bypassDocumentValidation

        • 類型:布爾
        • 描述
          • 僅當指定 $out 或 $merge 階段時適用
          • 使 aggregate() 方法繞過文檔數據校驗,允許管道處理階段插入不滿足數據校驗的文檔
      • readConcern

        • 類型:Document

        • 描述

          • 指定讀取策略

          • 格式

            readConcern: { level : <value> }
            
            • value 取值
              • "local"
              • "available"
              • "majority"
              • "linearizable"
      • collation

        • 類型:Document
        • 描述
          • 指定排序規則
      • hint

        • 類型:字符串或文檔
        • 描述
          • 指定聚合操作使用的索引
          • 可以通過索引名稱或索引規範文檔指定
      • comment

        • 類型:字符串
        • 描述
          • 指定字符串來幫助追蹤操作
          • 可以在 comment 中編碼任意信息,以便更容易地通過系統跟蹤或識別特定的操作,例如包含進程ID、線程ID、客戶端主機名、發出命令的用戶等
      • 可通過 database profiler、currentOp、logs 追蹤

      • writeConcern

        • 類型:Document
        • 描述:指定 $out、$merge 階段的寫入策略

Returns

  • 遊標,指向聚合管道最後階段生成的文檔
  • 遊標,如果包含 explain 選項,則指向關於聚合操作處理的詳細信息的文檔
  • 空遊標,如果管道中包含 $out 操作

示例

文檔

db.orders.insertMany([
{ _id: 1, cust_id: "abc1", ord_date: ISODate("2012-11-02T17:04:11.102Z"), status: "A", amount: 50 },
{ _id: 2, cust_id: "xyz1", ord_date: ISODate("2013-10-01T17:04:11.102Z"), status: "A", amount: 100 },
{ _id: 3, cust_id: "xyz1", ord_date: ISODate("2013-10-12T17:04:11.102Z"), status: "D", amount: 25 },
{ _id: 4, cust_id: "xyz1", ord_date: ISODate("2013-10-11T17:04:11.102Z"), status: "D", amount: 125 },
{ _id: 5, cust_id: "abc1", ord_date: ISODate("2013-11-12T17:04:11.102Z"), status: "A", amount: 25 }
])
  • group and sum

    > var res = db.orders.aggregate([
        { $match: { status: "A" } },
        { $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
        { $sort: { total: -1 } }
    ])
    
    > res
    { "_id" : "xyz1", "total" : 100 }
    { "_id" : "abc1", "total" : 75 }
    
    • 選擇狀態爲A的文檔 -->
    • 按 cust_id 字段對匹配的文檔進行分組,並計算每組中 amount 字段的總和 -->
    • 按 total 字段對結果進行降序排列
  • 顯示聚合管道執行計劃的詳細信息

    db.orders.explain().aggregate([
       { $match: { status: "A" } },
       { $group: { _id: "$cust_id", total: { $sum: "$amount" } } },
       { $sort: { total: -1 } }
    ])
    
  • 使用外部存儲處理大數據集

    var results = db.stocks.aggregate(
        [
            { $project : { cusip: 1, date: 1, price: 1, _id: 0 } },
            { $sort : { cusip : 1, date: 1 } }
        ],
        {
        allowDiskUse: true
        }
    )
    
  • 指定聚合操作使用的索引

    db.foodColl.createIndex( { qty: 1, type: 1 } );
    db.foodColl.createIndex( { qty: 1, category: 1 } );
    
    db.foodColl.aggregate(
       [ { $sort: { qty: 1 }}, { $match: { category: "cake", qty: 10  } }, { $sort: { type: -1 } } ],
       { hint: { qty: 1, category: 1 } }
    )
    

Pipeline Expressions

管道表達式

https://docs.mongodb.com/v4.2/core/aggregation-pipeline/#pipeline-expressions

for update

https://docs.mongodb.com/v4.2/tutorial/update-documents-with-aggregation-pipeline/

for Sharded Collections

https://docs.mongodb.com/v4.2/core/aggregation-pipeline-sharded-collections/#aggregation-pipeline-sharded-collection

vs Map-Reduce

  • 聚合管道是 map-reduce 的一種替代方案,對於複雜的聚合任務是首選解決方案

限制

  • 聚合管道對值類型和結果大小有一些限制

  • 結果大小限制

    • aggregate 命令

      • 聚合命令可以返回遊標,也可以將結果存儲在集合中,結果集中的每個文檔都受BSON文檔大小限制,當前爲 16MB,如果某個文檔大小超過BSON大小限制,則聚合命令將產生錯誤

        • 僅適用於結果集中返回的文檔,管道中的文檔不受此限制
        • MongoDB 3.6刪除了聚合命令以單個文檔的形式返回結果的選項
    • aggregate() 方法

      • 返回遊標
  • 內存大小限制

    • 聚合管道每個階段可以利用的RAM大小爲 100MB,超過內存限制則會產生錯誤
    • 對於大型數據集,可以在 aggregate() 方法中設置 allowDiskUse 選項,允許聚合管道操作鍵該數據寫入臨時文件中
    • 以下聚合操作只能使用內存
      • $graphLookup
      • $addToSet
      • push
    • 如果聚合管道的某個階段設置了 allowDiskUse:true 則對其他階段也生效

優化

聚合命令對單個集合進行操作,邏輯上將整個集合傳遞給聚合管道,爲了優化操作,應儘可能比表面掃描整個集合

  • 利用索引

    mongodb的查詢規劃器(query planner)分析聚合管道,以確定是否可以使用索引來提高某些階段的性能

    • $match
      • 如果 $match 處於管道開頭,則可利用索引篩選文檔,減少掃描文檔數量
    • $sort
      • 只要 $sort 前面沒有 $project、$unwind、$group 階段,則可利用索引排序
    • $group
      • 滿足以下條件,則 $group 可以利用索引查找每個分組中的第一個文檔
        1. 在 $sort 階段之後,且 $sort 階段對分組字段進行排序
        2. 在分組字段上有一個索引,與 $sort 排序一致
        3. 在 $group 階段中只使用 $first 累加器
    • $geoNear
  • 預先過濾

    • 當聚合操作只需針對集合中數據的一個子集,則在管道開頭使用 $match、$limit、$skip 等階段,限制輸入文檔的數量

    • 在管道開頭使用 $match 和 $sort 階段,邏輯上相當於一個帶有排序的查詢,並且可以使用索引,如果可能,儘量在管道開頭使用 $match 階段

Stages

$group

  • 按指定字段對集合中的文檔進行分組,每組輸出一個文檔,輸出文檔的_id字段包含唯一值

  • 類似 sql 中的 GROUP BY

  • 輸出文檔中還可以添加自定義字段,用於顯示累加器表達式值

  • 如果文檔不包含分組字段,則忽略該文檔

    區別於包含分組字段,但是值爲null

格式

db.collection.aggregate([
{
  $group:
    {
      _id: <expression>, 
      <field>: { <accumulator> : <expression> },
      ...
    }
 }
])
  • _id

    • 類型:表達式(字符串 "$<分組字段>" 或 操作符表達式)
    • 描述:指定分組字段
      • 如果指定爲 null 或 其他常量值(數字),則將整個集合視爲一組進行計算
  • field

    • 類型:字符串
    • 描述:自定義字段,用於顯示累加器表達式的值
  • <accumulator>

    • 描述:累加器(聚合)操作符

    • 常用聚合操作符

      操作符 描述
      $sum 利用 $group 分組後,對同組內的文檔,對指定字段的數值進行求和
      $avg 利用 $group 分組後,對同組內的文檔,對指定字段的數值求平均值
      $first 利用 $group 分組後,對同組內的文檔,顯示指定字段的第一個值
      $last 利用 $group 分組後,對同組內的文檔,顯示指定字段的最後一個值
      $max 利用 $group 分組後,對同組內的文檔,顯示指定字段的最大值
      $min 利用 $group 分組後,對同組內的文檔,顯示指定字段的最小值
      $push 利用 $group 分組後,對同組內的文檔,以數組的方式顯示指定字段
      $addToSet 利用 $group 分組後,對同組內的文檔,以數組的方式顯示字段不重複的值
  • <expression>

    • 類型:表達式(字符串 "$<計算的字段>" 或 操作符表達式)
    • 描述:累加器操作符計算的字段

示例

文檔

db.sales.insertMany([
  { "_id" : 1, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("2"), "date" : ISODate("2014-03-01T08:00:00Z") },
  { "_id" : 2, "item" : "jkl", "price" : NumberDecimal("20"), "quantity" : NumberInt("1"), "date" : ISODate("2014-03-01T09:00:00Z") },
  { "_id" : 3, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" : NumberInt( "10"), "date" : ISODate("2014-03-15T09:00:00Z") },
  { "_id" : 4, "item" : "xyz", "price" : NumberDecimal("5"), "quantity" :  NumberInt("20") , "date" : ISODate("2014-04-04T11:21:39.736Z") },
  { "_id" : 5, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("10") , "date" : ISODate("2014-04-04T21:23:13.331Z") },
  { "_id" : 6, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("5" ) , "date" : ISODate("2015-06-04T05:08:13Z") },
  { "_id" : 7, "item" : "def", "price" : NumberDecimal("7.5"), "quantity": NumberInt("10") , "date" : ISODate("2015-09-10T08:43:00Z") },
  { "_id" : 8, "item" : "abc", "price" : NumberDecimal("10"), "quantity" : NumberInt("5" ) , "date" : ISODate("2016-02-06T20:20:13Z") },
])
  • 計算集合中文檔的數量

    db.sales.aggregate( [
      {
        $group: {
           _id: null,
           count: { $sum: 1 }
        }
      }
    ] )
    
    • 返回

      { "_id" : null, "count" : 8 }
      
  • _id 設爲 null 或 常量

      db.sales.aggregate( [
      {
        $group: {
           _id: 404,
           count: { $sum: 1 }
        }
      }
    ] )
    
    • 返回

      { "_id" : 404, "count" : 8 }
      
  • 檢索某個字段的不同值

    db.sales.aggregate( [ { $group : { _id : "$item" } } ] )
    
  • having

    db.sales.aggregate(
      [
        // First Stage
        {
          $group :
            {
              _id : "$item",
              totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } }
            }
         },
         // Second Stage
         {
           $match: { "totalSaleAmount": { $gte: 100 } }
         }
       ]
     )
    
    • 按 item 字段進行分組
    • 計算每組的總銷售額
    • 返回總銷售額大於等於100的文檔
  • 多聚合操作符

    db.sales.aggregate([
      // First Stage
      {
        $match : { "date": { $gte: new ISODate("2014-01-01"), $lt: new ISODate("2015-01-01") } }
      },
      // Second Stage
      {
        $group : {
           // 多個 key-value 鍵值對
           _id : { $dateToString: { format: "%Y-%m-%d", date: "$date" } },
           totalSaleAmount: { $sum: { $multiply: [ "$price", "$quantity" ] } },
           averageQuantity: { $avg: "$quantity" },
           count: { $sum: 1 }
        }
      },
      // Third Stage
      {
        $sort : { totalSaleAmount: -1 }
      }
     ])
    
    • 選擇 日期範圍是 2014 年的文檔
    • 按照日期進行分組,每組計算銷售總額、平均值以及文檔數量
    • 按照銷售總額倒序排列

$project

  • 顯示並傳遞指定字段,包括文檔中的現有字段或新計算的字段(新增)

  • 對於嵌入式文檔中的字段,可以通過 點表示法 或 嵌套字段表示法

    "contact.address.country": <1 or 0 or expression>
    
    contact: { address: { country: <1 or 0 or expression> } }
    
  • 如果 $project 指定一個空文檔則報錯

格式

{ $project: { <specification(s)> } }
  • specifications

    • _id : <0 or false>

      • 排除_id字段
    • <field> : <0 or 1>

      • 1:包含指定字段

        • _id字段默認顯示和傳遞,其他字段默認不顯示並排除
        • 如果指定的字段不存在, $project 將忽略該字段(不會創建)
        • 不能同時指定包含和排除字段
      • 0:排除指定字段

        • 如果有條件的排除,需使用 REMOVE 變量

        • 如果排除_id 以外的所有字段,則不能使用任何其他的規範表單

          if you exclude fields, you cannot also specify the inclusion of fields, reset the value of existing fields, or add new fields.

        • _id字段外,其他字段默認不顯示,但如果顯式排除了某個字段,則其他所有字段將顯示並傳遞

    • <field> : <expression>

      • 增加一個新字段或重置已存在的字段
  • 常用操作符

    • 字符串操作符
    • 算數運算操作符
    • 時間日期操作符

示例

  • 排除嵌入式文檔中的指定字段

    文檔

    db.books.insert({
      "_id" : 1,
      title: "abc123",
      isbn: "0001122223334",
      author: { last: "zzz", first: "aaa" },
      copies: 5,
      lastModified: "2016-07-28"
    })
    

    排除

    db.books.aggregate( [ { $project : { "author.first" : 0, "lastModified" : 0 } } ] )
    
    db.bookmarks.aggregate( [ { $project: { "author": { "first": 0}, "lastModified" : 0 } } ] )
    
  • 包含嵌入式文檔中的指定字段

    文檔

    db.bookmarks.insertMany([
    { _id: 1, user: "1234", stop: { title: "book1", author: "xyz", page: 32 } },
    { _id: 2, user: "7890", stop: [ { title: "book2", author: "abc", page: 5 }, { title: "book3", author: "ijk", page: 100 } ] }
    ])
    

    包含

    db.bookmarks.aggregate( [ { $project: { "stop.title": 1 } } ] )
    
    db.bookmarks.aggregate( [ { $project: { stop: { title: 1 } } } ] )
    
    • 結果

      { "_id" : 1, "stop" : { "title" : "book1" } }
      { "_id" : 2, "stop" : [ { "title" : "book2" }, { "title" : "book3" } ] }
      
    • 多個值將自動以數組的形式返回

  • 新增字段

    db.books.aggregate(
       [
          {
             $project: {
                title: 1,
                isbn: {
                   prefix: { $substr: [ "$isbn", 0, 3 ] },
                   group: { $substr: [ "$isbn", 3, 2 ] }
                },
                lastName: "$author.last",
                copiesSold: "$copies"
             }
          }
       ]
    )
    
    • 結果

      {
      	"_id" : 1,
      	"title" : "abc123",
      	"isbn" : {
      		"prefix" : "000",
      		"group" : "11"
      	},
      	"lastName" : "zzz",
      	"copiesSold" : 5
      }
      
  • 新增數組

    文檔

    { "_id" : ObjectId("55ad167f320c6be244eb3b95"), "x" : 1, "y" : 1 }
    

    新增

    db.collection.aggregate( [ { $project: { myArray: [ "$x", "$y", "$someField" ] } } ] )
    
    • 結果

      { "_id" : ObjectId("55ad167f320c6be244eb3b95"), "myArray" : [ 1, 1, null ] }
      
    • 如果數組中引用了不存在的字段,則用 null 代替

$sort

  • 對所有的輸入文檔進行排序,返回排序後的文檔

格式

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
  • <sort order>
    • 1 :升序排序
    • -1:降序排列
  • 多字段排序,從左到右計算排序順序,先按 field1 排序,具有相同 field1 值的文檔再按 field2 排序

優化

  • 當 $sort 在 $limit 之前且中間沒有改變文檔數量的操作時,優化器可以將 $limit 合併到 $sort 中,即 $sort 操作在進行過程中只維護頂部的 n 個結果(n 爲 $limit 限定的值),mongodb 只在內存中存儲 n 個文檔

  • $sort 階段只能佔用 100 MB 的內存,默認超過則報錯。爲了處理大型數據集,設置 allowDiskUse 爲 true,允許 $sort 操作將數據寫入臨時文件

  • 如果管道前面沒有 $project、$unwind、$group 階段,則$sort 可利用索引進行排序

$skip

  • 跳過指定數量的文檔,返回剩餘的文檔
  • 對文檔內容無影響

格式

{ $skip: <positive integer> }
  • 值爲一個正整數

$limit

  • 限制返回的文檔數量,返回指定數量的文檔(按輸入順序)
  • 對文檔內容無影響

格式

{ $limit: <positive integer> }
  • 值爲一個正整數

$match

  • 篩選文檔,返回符合條件的文檔
  • 儘可能在管道開頭放置 $match 階段,限制輸入文檔數量
  • 管道開頭的 $match 可以像 find() findOne()那樣利用索引

格式

{ $match: { <query> } }
  • query:查詢條件表達式

限制

  1. $match 查詢語法與讀取操作查詢語法相同;例如:$match不接受原始聚合表達式。要在$match中包含聚合表達式,請使用$expr查詢表達式

    { $match: { $expr: { <aggregation expression> } } }
    
  2. 要在 $match 中使用 $text,則必須作爲管道的第一階段

示例

  • 相等匹配

    db.articles.aggregate(
        [ { $match : { author : "dave" } } ]
    );
    
  • 條件查詢

    db.articles.aggregate( [
      { $match: { $or: [ { score: { $gt: 70, $lt: 90 } }, { views: { $gte: 1000 } } ] } },
      { $group: { _id: null, count: { $sum: 1 } } }
    ] );
    
    • $match 選擇分數大於70小於90的文檔,或者視圖大於等於1000的文檔,這些文檔通過管道傳輸給 $group 階段

    • $group 統計文檔數量

    • 結果

      { "_id" : null, "count" : 5 }
      

$lookup

  • 對同一數據庫中未分片的集合進行左外連接,用於查找當前集合中與另一集合條件匹配的文檔

  • 相當於關係數據庫中的左外聯查詢

    左外聯:返回包括左表中的所有記錄和右表中符合查詢條件的記錄

    右外聯:返回包括右表中的所有記錄和左表中符合查詢條件的記錄

    https://blog.csdn.net/plg17/article/details/78758593

相等查詢
{
   $lookup:
     {
       from: <collection to join>,
       localField: <field from the input documents>,
       foreignField: <field from the documents of the "from" collection>,
       as: <output array field>
     }
}
  • from

    • 類型:字符串
    • 指定要與輸入集合進行左外聯的同一數據庫中的其他集合
  • localField

    • 類型:字符串
    • 管道輸入集合中需關聯的鍵
    • $lookup 將 localField 和 foreignField 進行相等匹配
    • 如果輸入集合中不包含 localField 指定的字段,則視 localField 值爲 null 與 foreignField 進行相等匹配
    • 如果localField 字段的類型爲數組,則數組元素依次匹配 foreignField
  • foreignField

    • 類型:字符串
    • from 集合中需關聯的鍵
    • $lookup 將 foreignField 和 localField 進行相等匹配
    • 如果 from 集合中不包含 foreignField 指定的字段,則視 foreignField 值爲 null 與 localField 進行相等匹配
  • as

    • 類型:字符串
    • 指定要添加到輸入文檔中的新數組字段的名稱
    • 該字段包含 from 集合中符合條件的文檔
    • 如果該字段名稱已經存在,則會覆蓋現有字段

示例

  • localField爲單一值

    文檔

    db.orders.insert([
       { "_id" : 1, "item" : "almonds", "price" : 12, "quantity" : 2 },
       { "_id" : 2, "item" : "pecans", "price" : 20, "quantity" : 1 },
       { "_id" : 3  }
    ])
    
    db.inventory.insert([
       { "_id" : 1, "sku" : "almonds", description: "product 1", "instock" : 120 },
       { "_id" : 2, "sku" : "bread", description: "product 2", "instock" : 80 },
       { "_id" : 3, "sku" : "cashews", description: "product 3", "instock" : 60 },
       { "_id" : 4, "sku" : "pecans", description: "product 4", "instock" : 70 },
       { "_id" : 5, "sku": null, description: "Incomplete" },
       { "_id" : 6 }
    ])
    

    通過 orders 集合中的 item 字段和 inventory 集合彙總的 sku 字段,將兩個集合連接起來

    db.orders.aggregate([
       {
         $lookup:
           {
             from: "inventory",
             localField: "item",
             foreignField: "sku",
             as: "inventory_docs"
           }
      }
    ])
    
    • 查詢 orders 集合中 item 字段的值與 inventory 集合中 sku 字段的值 相等的 文檔

    • 輸出文檔中的 inventory_docs 字段包含 inventory 集合中符合條件的文檔

    • 結果

      {
         "_id" : 1,
         "item" : "almonds",
         "price" : 12,
         "quantity" : 2,
         "inventory_docs" : [
            { "_id" : 1, "sku" : "almonds", "description" : "product 1", "instock" : 120 }
         ]
      }
      {
         "_id" : 2,
         "item" : "pecans",
         "price" : 20,
         "quantity" : 1,
         "inventory_docs" : [
            { "_id" : 4, "sku" : "pecans", "description" : "product 4", "instock" : 70 }
         ]
      }
      {
         "_id" : 3,  
         "inventory_docs" : [
            { "_id" : 5, "sku" : null, "description" : "Incomplete" },
            { "_id" : 6 }
         ]
      }
      

      orders 集合中的 { "_id" : 3 } 文檔不包含 item 字段,則$lookup將其視爲 null 匹配 inventory 集合中的 { "_id" : 5, "sku" : null, "description" : "Incomplete" }{ "_id" : 6 } 兩個文檔

  • localFIeld 爲數組

    文檔

    db.classes.insert( [
       { _id: 1, title: "Reading is ...", enrollmentlist: [ "giraffe2", "pandabear", "artie" ], days: ["M", "W", "F"] },
       { _id: 2, title: "But Writing ...", enrollmentlist: [ "giraffe1", "artie" ], days: ["T", "F"] }
    ])
    
    db.members.insert( [
       { _id: 1, name: "artie", joined: new Date("2016-05-01"), status: "A" },
       { _id: 2, name: "giraffe", joined: new Date("2017-05-01"), status: "D" },
       { _id: 3, name: "giraffe1", joined: new Date("2017-10-01"), status: "A" },
       { _id: 4, name: "panda", joined: new Date("2018-10-11"), status: "A" },
       { _id: 5, name: "pandabear", joined: new Date("2018-12-01"), status: "A" },
       { _id: 6, name: "giraffe2", joined: new Date("2018-12-01"), status: "D" }
    ])
    

    通過 classes 集合中的 enrollmentlist 字段 和 members 集合中的 name 字段,將兩個集合連接起來

    db.classes.aggregate([
       {
          $lookup:
             {
                from: "members",
                localField: "enrollmentlist",
                foreignField: "name",
                as: "enrollee_info"
            }
       }
    ])
    
    • 查詢 classes 集合中 enrollmentlist 數組中的元素跟 members 集合中 name 字段值相等的文檔

    • 輸出文檔中的 enrollee_info 字段包含 members 集合中符合條件的文檔

    • 結果

      {
         "_id" : 1,
         "title" : "Reading is ...",
         "enrollmentlist" : [ "giraffe2", "pandabear", "artie" ],
         "days" : [ "M", "W", "F" ],
         "enrollee_info" : [
            { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
            { "_id" : 5, "name" : "pandabear", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "A" },
            { "_id" : 6, "name" : "giraffe2", "joined" : ISODate("2018-12-01T00:00:00Z"), "status" : "D" }
         ]
      }
      {
         "_id" : 2,
         "title" : "But Writing ...",
         "enrollmentlist" : [ "giraffe1", "artie" ],
         "days" : [ "T", "F" ],
         "enrollee_info" : [
            { "_id" : 1, "name" : "artie", "joined" : ISODate("2016-05-01T00:00:00Z"), "status" : "A" },
            { "_id" : 3, "name" : "giraffe1", "joined" : ISODate("2017-10-01T00:00:00Z"), "status" : "A" }
         ]
      }
      
不相關子查詢與多條件查詢
{
   $lookup:
     {
       from: <collection to join>,
       let: { <var_1>: <expression>, …, <var_n>: <expression> },
       pipeline: [ <pipeline to execute on the collection to join> ],
       as: <output array field>
     }
}
  • from

    • 類型:字符串
    • 指定要與輸入集合進行左外聯的同一數據庫中的其他集合
  • left

    • 類型:文檔 let : { <引用變量名> : "$<輸入文檔中的字段>" }
    • 使用變量表達式訪問輸入文檔中的字段
    • 定義要在 pipeline 管道階段中使用的變量,用以訪問輸入 $lookup 階段的文檔中的字段
  • pipeline

    • 類型:數組

    • 指定要在 from 集合上運行的管道,用以篩選符合條件的文檔

      如要返回所有的文檔,則指定一個不含任何階段的空管道[ ]

    • pipeline 管道中不能包含 $out 和 $merge 階段

    • pipeline 管道階段 能 直接訪問 from 集合中的文檔字段,通過"$<from集合中的文檔字段>"

    • pipeline 管道階段 不能 直接訪問輸入文檔中的字段,必須首先在 let 子句中定義中間變量,然後才能在 pipeline 管道的各個階段中引用

      • 在 pipeline 管道階段中通過 $$<variable> 的形式引用變量
      • $match 階段需要通過 $expr 操作符來使用聚合表達式,訪問 let 子句中定義的變量
  • as

    • 類型:字符串
    • 指定要添加到輸入文檔中的新數組字段的名稱
    • 該字段包含 from 集合中符合條件的文檔
    • 如果該字段名稱已經存在,則會覆蓋現有字段

示例

  • 多條件查詢

    文檔

    db.orders.insert([
      { "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2 },
      { "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1 },
      { "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60 }
    ])
    
    db.warehouses.insert([
      { "_id" : 1, "stock_item" : "almonds", warehouse: "A", "instock" : 120 },
      { "_id" : 2, "stock_item" : "pecans", warehouse: "A", "instock" : 80 },
      { "_id" : 3, "stock_item" : "almonds", warehouse: "B", "instock" : 60 },
      { "_id" : 4, "stock_item" : "cookies", warehouse: "B", "instock" : 40 },
      { "_id" : 5, "stock_item" : "cookies", warehouse: "A", "instock" : 80 }
    ])
    

    通過 item字段 以及 條件(庫存數量是否滿足訂單數量),將orders集合和warehouses集合連接起來

    db.orders.aggregate([
       {
          $lookup:
             {
               from: "warehouses",
               let: { order_item: "$item", order_qty: "$ordered" },
               pipeline: [
                  { $match:
                     { $expr:
                        { $and:
                           [
                             { $eq: [ "$stock_item",  "$$order_item" ] },
                             { $gte: [ "$instock", "$$order_qty" ] }
                           ]
                        }
                     }
                  },
                  { $project: { stock_item: 0, _id: 0 } }
               ],
               as: "stockdata"
             }
        }
    ])
    
    • 根據 orders 集合中的 name 字段,查詢 warehouses 集合中庫存數量滿足訂單數量的文檔

    • 輸出文檔中的 stockdata 字段包含符合條件的 warehouses 集合中的文檔

    • 結果

      { "_id" : 1, "item" : "almonds", "price" : 12, "ordered" : 2,
         "stockdata" : [ { "warehouse" : "A", "instock" : 120 }, { "warehouse" : "B", "instock" : 60 } ] }
      { "_id" : 2, "item" : "pecans", "price" : 20, "ordered" : 1,
         "stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] }
      { "_id" : 3, "item" : "cookies", "price" : 10, "ordered" : 60,
         "stockdata" : [ { "warehouse" : "A", "instock" : 80 } ] }
      
  • 不相關子查詢

    子查詢或內部查詢

    • 嵌套在其它查詢中的查詢

    主查詢或外部查詢

    • 包含子查詢的查詢

    不相關子查詢

    • 內部查詢的執行獨立於外部查詢,內部查詢只執行一次,然後將結果作爲外部查詢的條件

    相關子查詢

    • 內部查詢的執行依賴於外部查詢的數據,外部查詢每執行一次,內部查詢也會執行一次。
    • 每次都是外部查詢先執行,將當前查詢數據傳遞給內部查詢,然後執行內部查詢,根據內部查詢的執行結果判斷當前數據是否滿足外部查詢的where條件,若滿足則當前數據是符合要求的記錄
    • 外部查詢依次掃描每條記錄,重複執行上述過程

    https://blog.csdn.net/qiushisoftware/article/details/80874463

$count

  • 返回輸入文檔的數量,並傳遞給下一階段
  • 不改變文檔內容

格式

{ $count: <string> }
  • string
    • 輸出字段的名稱,值爲輸入文檔的數量
    • 非空字符串,不能以$開頭,不能包含點.字符

示例

  • $count 行爲等價於 $group + $project

    db.collection.aggregate( [
       { $group: { _id: null, myCount: { $sum: 1 } } },
       { $project: { _id: 0 } }
    ] )
    
    db.collection.aggregate([
    {
    	$count:"myCount"
    }
    ])
    

$unwind

  • 解析輸入文檔中的數組字段,將每個元素作爲字段值輸出單獨的文檔

格式

{ $unwind: <field path> }
{
  $unwind:
    {
      path: <field path>,
      includeArrayIndex: <string>,
      preserveNullAndEmptyArrays: <boolean>
    }
}
  • path

    • 類型:字符串
    • 描述:指定要解析的數組字段
  • includeArrayIndex

    • 類型:字符串
    • 描述:新字段的名稱,用於保存元素在原數組中的索引,不能以 $ 開頭
  • preserveNullAndEmptyArrays

    preserve 保留

    • 類型:布爾

    • 描述:如果文檔不包含 path 指定的字段,或字段值爲null,或字段值爲空數組[ ]

      • true

        $unwind 原樣輸出該文檔

      • false【默認】

        $unwind 不輸出該文檔

示例

  • preserveNullAndEmptyArrays 默認false

    db.inventory2.insertMany([
      { "_id" : 1, "item" : "ABC", price: NumberDecimal("80"), "sizes": [ "S", "M", "L"] },
      { "_id" : 2, "item" : "EFG", price: NumberDecimal("120"), "sizes" : [ ] },
      { "_id" : 3, "item" : "IJK", price: NumberDecimal("160"), "sizes": "M" },
      { "_id" : 4, "item" : "LMN" , price: NumberDecimal("10") },
      { "_id" : 5, "item" : "XYZ", price: NumberDecimal("5.75"), "sizes" : null }
    ])
    

    展開

    db.inventory2.aggregate( [ { $unwind: "$sizes" } ] )
    db.inventory2.aggregate( [ { $unwind: { path: "$sizes" } } ] )
    

    兩種語法效果一樣

    結果

    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
    { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
    
    • size 字段丟失、爲null、爲空數組的文檔,默認不輸出
  • 記錄索引,輸出文檔

    db.inventory2.aggregate( [
      {
        $unwind:
          {
            path: "$sizes",
            includeArrayIndex: "arrayIndex",
            preserveNullAndEmptyArrays: true
          }
       }])
    

    結果

    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "S" }
    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "M" }
    { "_id" : 1, "item" : "ABC", "price" : NumberDecimal("80"), "sizes" : "L" }
    { "_id" : 2, "item" : "EFG", "price" : NumberDecimal("120") }
    { "_id" : 3, "item" : "IJK", "price" : NumberDecimal("160"), "sizes" : "M" }
    { "_id" : 4, "item" : "LMN", "price" : NumberDecimal("10") }
    { "_id" : 5, "item" : "XYZ", "price" : NumberDecimal("5.75"), "sizes" : null }
    
  • 解析嵌套數組

    • 先解析外層數組,再解析內層數組,path路徑需要通過點表示法引用內層數組
    {
        _id: "1",
        "items" : [
         {
          "name" : "pens",
          "tags" : [ "writing", "office", "school", "stationary" ],
          "price" : NumberDecimal("12.00"),
          "quantity" : NumberInt("5")
         },
         {
          "name" : "envelopes",
          "tags" : [ "stationary", "office" ],
          "price" : NumberDecimal("1.95"),
          "quantity" : NumberInt("8")
         }
        ]
      }
    
    db.sales.aggregate([
    
      // First Stage
      { $unwind: "$items" },
    
      // Second Stage
      { $unwind: "$items.tags" }
      
     ])
    

$out

  • 將聚合操作的結果寫入指定的集合

  • $out 必須是管道的最後一個階段

  • 不能寫入固定集合中

  • 如果指定的集合不存在,則在完成聚合操作後,會創建該集合

    在聚合操作完成之前,該集合是不可見的。如果聚合操作失敗,則不會創建

  • 如果指定的集合已經存在,則在完成聚合操作後,會用聚合操作結果覆蓋原有數據

  • $out 操作符不會改變原集合上建立的索引,如果聚合操作的結果文檔違反任一唯一索引(包括原集合_id字段上建立的索引),則寫入失敗

格式

{ $out: "<output-collection>" }
  • output-collection
    • 輸出集合的名稱

示例

db.books.insert([
{ "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 },
{ "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 },
{ "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 },
{ "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 },
{ "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }
])

聚合

db.books.aggregate( [
                      { $group : { _id : "$author", books: { $push: "$title" } } },
                      { $out : "authors" }
                  ] )

結果在當前數據庫中新增 authors 集合

{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] }
{ "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }

$merge

$addFields

  • 向輸入文檔中添加新字段,返回包含所有原有字段和新添加字段的文檔
  • 相當於 $project 顯式輸出所有原有字段並添加新字段
  • mongodb 4.2 新增 $set 階段是 $addFields 的別名

格式

{ $addFields: { <newField>: <expression>, ... } }
  • newField
    • 新增字段名
    • 如果已存在該字段,則覆蓋原有字段

Operators

## 僅 Group ##

$push

  • 利用 $group 分組後,對同組內的文檔,以數組的形式,返回指定字段的值
  • 只能應用於 $group 階段

格式

{ $push: <expression> }

$addToSet

  • 利用 $group 分組後,對同組內的文檔,以數組的形式,返回指定字段 不重複的值
  • 只能應用於 $group 階段

格式

{ $addToSet: <expression> }
  • 如果 expression 解析爲某個字段的值"$<field>",則以數組的形式返回該字段不重複的值

$sum

  • 利用 $group 分組後,對同組內的文檔,對指定字段的數值進行求和

    {$sum:1}

    • 表示計算文檔數量總和,管道中的每個文檔代表數值1

    • 在 $group 階段,分組過程中,計算每組文檔的數量

  • 3.2 版本及之前,僅用於 $group 階段

格式

$group 階段

{ $sum: <expression> }

其他階段

{ $sum: <expression> }
{ $sum: [ <expression1>, <expression2> ... ]  }
  • 非數字或不存在字段

    Example Field Values Results
    { $sum : <field> } Numeric Sum of Values
    { $sum : <field> } Numeric and Non-Numeric Sum of Numeric Values
    { $sum : <field> } Non-Numeric or Non-Existent 0
    • 在包含數字和非數字的字段上使用,則忽略非數字字段,返回數字值之和
    • 如果字段不存在,則返回0
    • 如果所有操作數都是非數字,則返回0
  • 數組字段

    • $group 階段,如果表達式解析爲數組,則視爲非數字值
    • 其他階段
      • 單個表達式作爲操作數,解析爲數組時,$sum 遍歷數組,對數字值求和並返回
      • 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $sum 不會遍歷數組,視爲非數字值

$avg

  • 利用 $group 分組後,對同組內的文檔,對指定字段的數值求平均值

  • 3.2 版本及之前,僅用於 $group 階段

格式

$group 階段

{ $avg: <expression> }

其他階段

{ $avg: <expression> }
{ $avg: [ <expression1>, <expression2> ... ]  }
  • 忽略非數字值和丟失的值
  • 如果操作數都是非數字值,則返回 null
  • 數組字段
    • $group 階段,如果表達式解析爲數組,則視爲非數字值
    • 其他階段
      • 單個表達式作爲操作數,解析爲數組時,$sum 遍歷數組,對數字值求和並返回
      • 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $sum 不會遍歷數組,視爲非數字值

$first

  • 利用 $group 分組後,對同組內的文檔,返回 指定字段 的第一個值

  • 一般在文檔排序後使用纔有意義

    • 當在 $group 階段中使用$first時,$group 階段應該在 $sort 階段之後,以使輸入文檔按照已定義的順序

    • 儘管 $sort 階段將有序的文檔作爲輸入傳遞到 $group 階段,但 $group 不能保證在其自己的輸出中維護這種排序順序。

  • 只能應用於 $group 階段

格式

{ $first: <expression> }

$last

  • 利用 $group 分組後,對同組內的文檔,返回 指定字段 的最後一個值

  • 一般在文檔排序後使用纔有意義

    當在 $group 階段中使用$first時,$group 階段應該在 $sort 階段之後,以使輸入文檔按照已定義的順序

  • 只能應用於 $group 階段

格式

{ $last: <expression> }

$max

  • 利用 $group 分組後,對同組內的文檔,返回 指定字段 的最大值
  • 3.2 版本及之前,僅用於 $group 階段

格式

$group 階段

{ $max: <expression> }

其他階段

{ $max: <expression> }
{ $max: [ <expression1>, <expression2> ... ]  }
  • 忽略 null 值和丟失字段,僅考慮字段的非空值和非缺失值
  • 如果所有字段的值都爲 null 或缺失,則返回 null
  • 數組字段
    • 在$group階段,如果表達式解析爲一個數組,則$max不會遍歷該數組並將該數組作爲一個整體進行比較。
    • 其他階段
      • 單個表達式作爲操作數,解析爲數組時,$max 遍歷數組,對數字值進行操作並返回元素最大值
      • 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $max 不會遍歷數組,視爲非數字值

$min

  • 利用 $group 分組後,對同組內的文檔,返回 指定字段 的最小值
  • 3.2 版本及之前,僅用於 $group 階段

格式

$group 階段

{ $min: <expression> }

其他階段

{ $min: <expression> }
{ $min: [ <expression1>, <expression2> ... ]  }
  • 忽略 null 值和丟失字段,僅考慮字段的非空值和非缺失值
  • 如果所有字段的值都爲 null 或缺失,則返回 null
  • 數組字段
    • 在$group階段,如果表達式解析爲一個數組,則 $min 不會遍歷該數組並將該數組作爲一個整體進行比較。
    • 其他階段
      • 單個表達式作爲操作數,解析爲數組時,$min 遍歷數組,對數字值進行操作並返回元素最小值
      • 多個表達式作爲操作數,如果某個表達式解析爲數組,則 $min 不會遍歷數組,視爲非數字值

## 條件判斷 ##

$switch

  • 對指定字段進行一系列的條件判斷,當條件爲true時,執行對應的表達式並跳出控制流
  • 各個case語句不需要互相排斥,$switch 執行第一個計算結果爲true的分支
  • 以下情況,$switch報錯
    1. branches 字段缺失或不是一個數組
    2. 條件語句不包含 case 字段
    3. 條件語句不包含 then 字段
    4. 條件語句包含 case、then 之外的字段
    5. 未指定 default 表達式且沒有條件語句計算結果爲true

格式

$switch: {
   branches: [
      { case: <expression>, then: <expression> },
      { case: <expression>, then: <expression> },
      ...
   ],
   default: <expression>
}
  • branches

    • 類型:文檔數組

    • 描述:

      • 控制分支,每個分支都必須具有 case 和 then 字段

        • case

          值爲可以解析爲布爾值的任何有效的 表達式,如果不是則強制轉換爲布爾值

        • then

          值爲任何有效的表達式

      • 必須至少包含一個條件分支

  • default【可選】

    • 沒有條件分支結果爲true時,執行默認表達式
    • 如果沒有指定,且沒有條件分支爲true,則 $switch 報錯

示例

文檔

db.grades.insert([
{ "_id" : 1, "name" : "Susan Wilkes", "scores" : [ 87, 86, 78 ] },
{ "_id" : 2, "name" : "Bob Hanna", "scores" : [ 71, 64, 81 ] },
{ "_id" : 3, "name" : "James Torrelio", "scores" : [ 91, 84, 97 ] }
])

聚合

db.grades.aggregate( [
  {
    $project:
      {
        "name" : 1,
        "summary" :
        {
          $switch:
            {
              branches: [
                {
                  case: { $gte : [ { $avg : "$scores" }, 90 ] },
                  then: "Doing great!"
                },
                {
                  case: { $and : [ { $gte : [ { $avg : "$scores" }, 80 ] },
                                   { $lt : [ { $avg : "$scores" }, 90 ] } ] },
                  then: "Doing pretty well."
                },
                {
                  case: { $lt : [ { $avg : "$scores" }, 80 ] },
                  then: "Needs improvement."
                }
              ],
              default: "No scores found."
            }
         }
      }
   }
] )
  • 結果

    { "_id" : 1, "name" : "Susan Wilkes", "summary" : "Doing pretty well." }
    { "_id" : 2, "name" : "Bob Hanna", "summary" : "Needs improvement." }
    { "_id" : 3, "name" : "James Torrelio", "summary" : "Doing great!" }
    

## 數組 ##

$size

  • 計算並返回數組的長度

格式

{ $size: <expression> }
  • expression

    可以解析爲數組的任何有效的表達式

## 字符串 ##

$substr

3.4 版本中被廢棄,現在是 substrBytes 的別名

  • 返回字符串中的子字符串,從指定索引位置開始,包含指定數目的字符。索引從0開始

格式

{ $substr: [ <string>, <start>, <length> ] }
  • 值爲 數組
  • string 一般是字段路徑 $<字段名>
  • 如果 start 是負數,則返回空字符串
  • 如果 length 是負數,則返回從指定索引位置開始到末尾的子字符串

$indexOfBytes

  • 查詢並返回子字符串在字段中第一次出現位置的索引,如果沒有找到則返回 -1

    UTF-8 字節索引

格式

{ $indexOfBytes: [ <string expression>, <substring expression>, <start>, <end> ] }
  • string expression
    • 可以解析爲字符串的任何有效的表達式
    • 如果表達式解析爲 null 或引用丟失的字段,則 $indexOfBytes 返回 null
    • 非以上情況返回一個錯誤
  • substring expression
    • 可以解析爲字符串的任何有效的表達式
  • start
    • 指定搜索的起始索引位置
    • 非負整數,從0開始
  • end
    • 指定搜索的結束索引位置
    • 非負整數
    • 指定 end,則必須同時指定 start

示例

db.inventory.insert([
{ "_id" : 1, "item" : "foo" },
{ "_id" : 2, "item" : "fóofoo" },
{ "_id" : 3, "item" : "the foo bar" },
{ "_id" : 4, "item" : "hello world fóo" },
{ "_id" : 5, "item" : null },
{ "_id" : 6, "amount" : 3 }
])

聚合

db.inventory.aggregate(
   [
     {
       $project:
          {
            byteLocation: { $indexOfBytes: [ "$item", "foo" ] },
          }
      }
   ]
)
  • 結果

    { "_id" : 1, "byteLocation" : "0" }
    { "_id" : 2, "byteLocation" : "4" }
    { "_id" : 3, "byteLocation" : "4" }
    { "_id" : 4, "byteLocation" : "-1" }
    { "_id" : 5, "byteLocation" : null }
    { "_id" : 6, "byteLocation" : null }
    
    • 注意 fóofoo 索引是4

      é is encoded using two bytes.

    • 每個文檔都有一個返回結果

$strLenBytes

  • 返回指定字符串 UTF-8 編碼的字節數(字符串長度)
  • 英文字母,使用 1個字節編碼(空串 0字節)
  • 中文、日文、韓文,使用 3字節編碼
  • 帶有變音符號的字符以及英語字母表之外的拉丁字符,使用2個字節編碼

格式

{ $strLenBytes: <string expression> }
  • string expression
    • 可以解析爲字符串的任何有效的表達式
    • 如果表達式解析爲 null 或引用丟失的字段,則 $strLenBytes 返回一個錯誤

示例

文檔

{ "_id" : 1, "name" : "apple" }
{ "_id" : 2, "name" : "banana" }
{ "_id" : 3, "name" : "éclair" }
{ "_id" : 4, "name" : "hamburger" }
{ "_id" : 5, "name" : "jalapeño" }
{ "_id" : 6, "name" : "pizza" }
{ "_id" : 7, "name" : "tacos" }
{ "_id" : 8, "name" : "壽司" }

聚合

db.food.aggregate(
  [
    {
      $project: {
        "name": 1,
        "length": { $strLenBytes: "$name" }
      }
    }
  ]
)
  • 結果

    { "_id" : 1, "name" : "apple", "length" : 5 }
    { "_id" : 2, "name" : "banana", "length" : 6 }
    { "_id" : 3, "name" : "éclair", "length" : 7 }
    { "_id" : 4, "name" : "hamburger", "length" : 9 }
    { "_id" : 5, "name" : "jalapeño", "length" : 9 }
    { "_id" : 6, "name" : "pizza", "length" : 5 }
    { "_id" : 7, "name" : "tacos", "length" : 5 }
    { "_id" : 8, "name" : "壽司", "length" : 6 }
    

$strcasecmp

  • 對兩個字符串執行不區分大小寫的比較

  • 僅適用於 ASCII 字符編碼

格式

{ $strcasecmp: [ <expression1>, <expression2> ] }
  • expression
    • 可以解析爲字符串的任何有效的表達式
  • 返回結果
    • 1 :第一個字符串 大於 第二個字符串
    • 0 :兩個字符串相等
    • -1:第一個字符串 小於 第二個字符串

$toLower

  • 將字符串轉換爲小寫字母並返回
  • 僅適用於 ASCII 字符編碼

格式

{ $toLower: <expression> }
  • expression
    • 可以解析爲字符串的任何有效的表達式
    • 如果表達式解析爲null,則返回空串

示例

db.inventory.aggregate(
   [
     {
       $project:
         {
           item: { $toLower: "$item" },
           description: { $toLower: "$description" }
         }
     }
   ]
)

$toUpper

  • 將字符串轉換爲大寫字母並返回

  • 僅適用於 ASCII 字符編碼

格式

{ $toUpper: <expression> }
  • expression
    • 可以解析爲字符串的任何有效的表達式
    • 如果表達式解析爲null,則返回空串

$concat

  • 合併字符串並返回

格式

{ $concat: [ <expression1>, <expression2>, ... ] }
  • expression
    • 可以解析爲字符串的任何有效的表達式
    • 如果表達式解析爲 null 或引用丟失的字段,則返回 null

$split

  • 根據指定的分隔符將字符串劃分爲子字符串數組
  • 如果在字符串中沒有找到分隔符,則將原字符串作爲數組的唯一元素返回
  • 返回的字符串數組中會忽略分隔符

格式

{ $split: [ <string expression>, <delimiter> ] }
  • expression
    • 類型:字符串
    • 描述:被分隔的字符串
      • 可以解析爲字符串的任何有效的表達式
  • delimiter
    • 類型:字符串
    • 描述:指定分隔符
      • 可以解析爲字符串的任何有效的表達式
Example Results
{ $split: [ "June-15-2013", "-" ] } [ "June", "15", "2013" ]
{ $split: [ "banana split", "a" ] } [ "b", "n", "n", " split" ]
{ $split: [ "Hello World", " " ] } [ "Hello", "World" ]
{ $split: [ "astronomical", "astro" ] } [ "", "nomical" ]
{ $split: [ "pea green boat", "owl" ] } [ "pea green boat" ]
{ $split: [ "headphone jack", 7 ] } Errors with message: ...
{ $split: [ "headphone jack", /jack/ ] } Errors with message: ...

"$split requires an expression that evaluates to a string as a second argument, found: regex"

## 比較運算 ##

$gt

  • 比較兩個值並返回一個布爾值

格式

{ $gt: [ <expression1>, <expression2> ] }
  • 返回值
    • true:第一個表達式大於第二個表達式
    • false:第一個表達式小於等於第二個表達式
  • 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型

$gte

  • 比較兩個值並返回一個布爾值

格式

{ $gte: [ <expression1>, <expression2> ] }
  • 返回值
    • true:第一個表達式大於等於第二個表達式
    • false:第一個表達式小於第二個表達式
  • 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型

$lt

  • 比較兩個值並返回一個布爾值

格式

{ $lt: [ <expression1>, <expression2> ] }
  • 返回值
    • true:第一個表達式小於第二個表達式
    • false:第一個表達式大於等於第二個表達式
  • 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型

$lte

  • 比較兩個值並返回一個布爾值

格式

{ $lte: [ <expression1>, <expression2> ] }
  • 返回值
    • true:第一個表達式小於等於第二個表達式
    • false:第一個表達式大於第二個表達式
  • 對於不同類型的值,使用指定的 BSON 比較順序來比較值和類型

## 算術運算 ##

$add

  • 對數值類型或日期類型的字段進行加法運算
  • 如果其中一個參數是日期類型,則將其他參數當作毫秒數相加並返回日期

格式

{ $add: [ <expression1>, <expression2>, ... ] }
  • expression
    • 可以解析爲數值或日期的任何有效的表達式

$subtract

  • 對數值類型或日期類型的字段進行減法運算
  • 兩個參數都是日期,則轉換爲毫秒數,返回差值
  • 一個參數是日期,則將其他參數當作毫秒數相減並返回日期

格式

{ $subtract: [ <expression1>, <expression2> ] }
  • expression
    • 可以解析爲數值或日期的任何有效的表達式
    • 第一個參數減去第二個參數
  • 要從日期中減去一個數值,則日期必須是第一個參數

示例

文檔

db.sales.insertMany([
   { "_id" : 1, "item" : "abc", "price" : 10, "fee" : 2, "discount" : 5, "date" : ISODate("2014-03-01T08:00:00Z") },
   { "_id" : 2, "item" : "jkl", "price" : 20, "fee" : 1, "discount" : 2, "date" : ISODate("2014-03-01T09:00:00Z") }
])

聚合

db.sales.aggregate( [ { $project: { item: 1, dateDifference: { $subtract: [ "$date", 5 * 60 * 1000 ] } } } ] )
  • 結果

    { "_id" : 1, "item" : "abc", "dateDifference" : ISODate("2014-03-01T07:55:00Z") }
    { "_id" : 2, "item" : "jkl", "dateDifference" : ISODate("2014-03-01T08:55:00Z") }
    

$multiply

  • 將數字相乘並返回結果
  • 對數值類型的字段進行乘法運算

格式

{ $multiply: [ <expression1>, <expression2>, ... ] }
  • expression
    • 可以解析爲數值的任何有效的表達式

$divide

  • 對數值類型的字段進行除法運算

格式

{ $divide: [ <expression1>, <expression2> ] }
  • expression

    • 可以解析爲數值的任何有效的表達式
    • 第一個參數除以第二個參數
  • 結果可以保留很多位小數

    { "_id" : 1, "item" : "abc", "dateDiff" : NumberDecimal("3.333333333333333333333333333333333") }
    

$mod

  • 對數值類型的字段進行取模運算(取餘數)

格式

{ $mod: [ <expression1>, <expression2> ] }
  • expression
    • 可以解析爲數值的任何有效的表達式
    • 第一個參數除以第二個參數

## 時間日期 ##

$year

  • 返回日期的年份部分
  • 如果值爲字符串,則報錯

格式

{ $year: <dateExpression> }
  • dateExpression【該部分操作符的格式具有相同的 dateExpression 規範】

    • 可以解析爲 日期、時間戳、ObjectID 的有效表達式

    • 或具有如下格式的文檔

      { date: <dateExpression>, timezone: <tzExpression> }
      
      • date

        • $year 操作符獲取的日期
        • dateExpression
          • 可以解析爲 日期、時間戳、ObjectID 的有效表達式
      • timezone【可選】

        • 指定時區,默認以 UTC 格式顯示

示例

Example Result
{ $year: new Date("2016-01-01") } 2016
{ $year: { date: new Date("Jan 7, 2003") } } 2003
{ $year: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } 2011
{ $year: ISODate("1998-11-07T00:00:00Z") } 1998
{ $year: { date: ISODate("1998-11-07T00:00:00Z"), timezone: "-0400" } } 1998
{ $year: "March 28, 1976" } error
{ $year: Date("2016-01-01") } error
{ $year: "2009-04-09" } error

示例

{
  "_id" : 1,
  "date" : ISODate("2014-01-01T08:15:39.736Z")
}

聚合

db.sales.aggregate(
   [
     {
       $project:
         {

           year: { $year: "$date" },

           month: { $month: "$date" },
           day: { $dayOfMonth: "$date" },
           hour: { $hour: "$date" },
           minutes: { $minute: "$date" },
           seconds: { $second: "$date" },
           milliseconds: { $millisecond: "$date" },
           dayOfYear: { $dayOfYear: "$date" },
           dayOfWeek: { $dayOfWeek: "$date" },
           week: { $week: "$date" }
         }
     }
   ]
)
  • 結果

    {
      "_id" : 1,
      "year" : 2014,
      "month" : 1,
      "day" : 1,
      "hour" : 8,
      "minutes" : 15,
      "seconds" : 39,
      "milliseconds" : 736,
      "dayOfYear" : 1,
      "dayOfWeek" : 4,
      "week" : 0
    }
    

$month

  • 返回日期的月份部分(值爲1~12之間)

格式

{ $month: <dateExpression> }

示例

Example Result
{ $month: new Date("2016-01-01") } 1
{ $month: { date: new Date("Nov 7, 2003") } } 11
{ $month: ISODate("2000-01-01T00:00:00Z") } 1
{ $month: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } 8
{ $month: { date: ISODate("2000-01-01T00:00:00Z"), timezone: "-0500" } } 12
{ $month: "March 28, 1976" } error
{ $month: { date: Date("2016-01-01"), timezone: "-0500" } } error
{ $month: "2009-04-09" } error

$week

  • 返回日期處於一年中的第幾周(值爲 0~53之間)
  • 星期從星期日開始,第一週從一年中的第一個星期日開始,之前的日子在第0周

格式

{ $week: <dateExpression> }

示例

Example Result
{ $week: new Date("Jan 1, 2016") } 0
{ $week: { date: new Date("2016-01-04") } } 1
{ $week: { date: new Date("August 14, 2011"), timezone: "America/Chicago" } } 33
{ $week: ISODate("1998-11-01T00:00:00Z") } 44
{ $week: { date: ISODate("1998-11-01T00:00:00Z"), timezone: "-0500" } } 43
{ $week: "March 28, 1976" } error
{ $week: Date("2016-01-01") } error
{ $week: "2009-04-09" } error

NOTE

$hour cannot take a string as an argument.

$hour

  • 返回日期的小時部分(值爲 0~23 之間)

格式

{ $hour: <dateExpression> }

$minute

  • 返回日期的分鐘部分(值爲0~59之間)

格式

{ $minute: <dateExpression> }

$second

  • 返回日期的秒數部分(值爲0~59之間,閏秒60)

格式

{ $second: <dateExpression> }

$millisecond

  • 返回日期的毫秒部分(值爲0~999之間)

格式

{ $millisecond: <dateExpression> }

$dayOfYear

  • 返回日期處於一年中的第幾天(值爲1~366之間)

格式

{ $dayOfYear: <dateExpression> }

$dayOfMonth

  • 返回日期處於這月中的第幾天(值爲1~31之間)

格式

{ $dayOfMonth: <dateExpression> }

$dayOfWeek

  • 返回日期處於這週中的第幾天(值爲1~7)

格式

{ $dayOfWeek: <dateExpression> }

map-reduce

  • 是一種數據處理範式(典範模式),將大量數據處理壓縮爲有用的聚合結果

  • 處理過程

    • 第一階段——映射(map)

      將輸入文檔中 指定字段 的值保存到一個數組中(value),映射到 分組字段的值(key),以鍵值對(key-value)的形式輸出至 reduce 階段

    • 第二階段——歸約(reduce)

      根據需求對 相同鍵 的值 做運算,將運算結果作爲 文檔返回 或 寫入集合

  • map-reduce 使用定製的 JavaScript函數——map 函數,將值(指定字段)映射到一個鍵(分組字段),如果一個鍵有多個值,reduce 函數會將其處理壓縮爲一個對象

  • 支持在分片集合上執行 map-reduce 操作,從 mongodb 4.2 開始

    • 若要輸出到分片集合,需提前創建
    • 不建議替換已存在的分片集合
  • 視圖不支持 map-reduce 操作

  • vs 聚合管道

    • 對大多數聚合操作,聚合管道提供更好的性能和更一致的接口,但是 map-reduce 操作提供了一些目前在聚合管道中無法提供的靈活性

    • map-reduce 的結果文檔中只包含新增字段,聚合管道可以通過 $project 控制結果文檔中包含原有字段

      reduce 函數返回結果文檔 { "_id": "<key>", "value": <執行結果> }

      可以在 finalize 函數中添加新字段

格式

db.collection.mapReduce(
                         <map>,								   // 類似 $group
                         <reduce>,
                         {
                           out: <collection>,		  // 類似 $out
                           query: <document>,	// 類似 $match
                           sort: <document>,       // 類似 $sort
                           limit: <number>,			  // 類似 $limit
                           finalize: <function>,     // 類似 $project
                           scope: <document>,
                           jsMode: <boolean>,
                           verbose: <boolean>,
                           bypassDocumentValidation: <boolean>
                         }
                       )
  • map

    • 類型:function

    • 描述:

      • JavaScript函數,將輸入文檔中 指定字段的值 保存到一個數組中(value),映射到 分組字段的值(key),輸出至 reduce 函數中

      • 作用於每個輸入文檔,以分組字段的值爲鍵(唯一),以指定字段的值爲value,將值作爲數組元素映射到鍵。執行完所有輸入文檔後,將組成的鍵值對輸出至reduce函數中

        相同的鍵只產生一個鍵值對

    • 格式

      function() {
         ...
         emit(key, value);
      }
      
      • 在 map 函數中,通過this引用當前文檔

      • map 函數不能訪問數據庫

      • map 函數不能對函數外產生影響

      • 可以訪問在 scope 參數中定義的全局變量

      • 一個 emit 只能容納 mongodb 最大 BSON 文檔大小的一半(8MB)

      • map 函數可以多次調用 emit( key, value) 函數,創建鍵值對

        • 如果 key 已經存在,則將 value 加入key對應的 value數組中

        • 如果 key 不存在,則創建新的 key-value 鍵值對

        根據輸入文檔字段的值,調用 0 次或1次 emit 函數

        function() {
            if (this.status == 'A')
                emit(this.cust_id, 1);
        }
        

        根據輸入文檔數組字段的元素個數,多次調用 emit 函數

        function() {
            this.items.forEach(function(item){ emit(item.sku, 1); });
        }
        
  • reduce

    • 類型:function

    • 描述:JavaScript函數,將與鍵關聯的值數組處理壓縮爲一個對象

    • 格式

      function(key, values) {
         ...
         return result;
      }
      
      • reduce 函數不能訪問數據庫

      • reduce 函數不能對函數外產生影響

      • values 類型爲數組,元素爲映射到鍵的值對象

      • 可以訪問在 scope 參數中定義的全局變量

      • 輸入 reduce 函數的文檔,其大小不能大於mongodb最大BSON文檔大小的一半(8MB)

        但是reduce函數處理過程中可以超過

      • 可以多次調用 reduce 函數,對於指定的鍵,前一個reduce函數的輸出將作爲下一個reduce函數的輸入

      • 對同一個鍵多次調用reduce函數,或需要執行 finalize 函數,此時

        • reduce 返回值的類型必須與map函數輸出值的類型一致,即鍵值對形式

        • reduce 函數必須是可結合的,以下聲明必須爲true

          reduce(key, [ C, reduce(key, [ A, B ]) ] ) == reduce( key, [ C, A, B ] )
          
        • reduce 函數必須是冪等的,以下聲明必須爲true

          reduce( key, [ reduce(key, valuesArray) ] ) == reduce( key, valuesArray )
          
        • reduce 函數應該是可交換的,values 數組中的元素順序不應該影響輸出結果,以下聲明爲true

          reduce( key, [ A, B ] ) == reduce( key, [ B, A ] )
          
  • options

    • out

      • 類型:字符串或Document
    • 描述:指定 map-reduce 操作結果的輸出形式,可以輸出到指定集合中,也可以內聯輸出

    • 格式

      輸出到指定集合

      out: <collectionName>
      
      • 如果集合已經存在,則會 替換 現有文檔

      指定輸出模式

      out: { <action>: <collectionName>
                	[, db: <dbName>]
                	[, sharded: <boolean> ]
                	[, nonAtomic: <boolean> ] 
                }
      
      • action

        • replace
          • 如果輸出集合已經存在,則用結果文檔 替換 現有文檔
        • merge
          • 如果輸出集合已經存在,則將結果文檔與現有文檔合併,如果現有文檔與結果文檔具有相同的鍵,則覆蓋現有文檔
        • reduce
          • 如果輸出集合已經存在,則將結果文檔與現有文檔合併。如果現有文檔與結果文檔具有相同的鍵,則對結果文檔和現有文檔應用reduce函數,並用結果覆蓋現有文檔
      • db

        • 指定 map-reduce 輸出集合所在的數據庫

        • 默認爲與輸入集合相同的數據庫

      • sharded

        • 4.2 被廢棄
      • nonAtomic

        • 將輸出操作指定爲非原子操作,只適用於 merge 和 reduce 輸出模式,需要幾分鐘的時間來執行
        • 默認情況下,nonAtomic 爲 false, map-reduce操作在後處理期間鎖定數據庫
        • 如果 nonAtomic 爲 true,後處理步驟將防止 MongoDB 鎖定數據庫,在此期間,其他客戶端將能夠讀取輸出集合的中間狀態

      內聯輸出

      out: { inline: 1 }
      
  • query

    • 類型:Document
    • 描述
      • 使用查詢操作符指定查詢條件,對輸入文檔進行篩選
    • 在 map 函數之前執行
  • sort

    • 類型:Document

    • 描述

      • 對輸入文檔進行排序,有利於優化 map-reduce 操作

        例如將排序鍵指定爲與 emit 相同的鍵

      • 指定排序的鍵必須是集合中已經建立索引的鍵

      • 在 map 函數之前執行

  • limit

    • 類型:number
    • 描述
      • 限制輸入文檔的數量
    • 在map函數之前執行
  • finalize

    • 類型:function

    • 描述

      • 對 reduce 輸出結果進行修改
      • 在 reduce 函數之後執行
    • 格式

      function(key, reducedValue) {
         ...
         return modifiedObject;
      }
      
      • finalize 函數不能訪問數據庫
      • finalize 函數不能對函數外產生影響
      • 可以訪問在 scope 參數中定義的全局變量
  • scope

    • 類型:Document
    • 描述
    • 定義可在 map、reduce、finalize 函數中訪問的全局變量
  • jsMode

    • 類型:布爾
    • 描述:是否在 map-reduce 過程中將數據轉換成 BSON 格式
    • false【默認】
  • verbose

    • 類型:布爾
    • 描述:是否在結果信息中包含時間信息
    • false【默認】
  • collation

    • 類型:Document
    • 描述:指定排序規則
  • bypassDocumentValidation

    • 類型:布爾
    • 描述:在 map-reduce 過程中是否繞過數據驗證,允許插入不滿足數據驗證的文檔

JS Function

map-reduce 操作和 $where 操作符表達式不能訪問某些全局函數或屬性,例如db

以下函數和屬性可以訪問

Available Properties Available Functions
args assert() isNumber() print()
MaxKey BinData() isObject() printjson()
MinKey DBPointer() ISODate() printjsononeline()
DBRef() isString() sleep()
doassert() Map() Timestamp()
emit() MD5() tojson()
gc() NumberInt() tojsononeline()
HexData() NumberLong() tojsonObject()
hex_md5() ObjectId() UUID()
version()

示例

普通文檔

計算每個消費者的總價

以 cust_id字段 分組,計算每組中 price字段的總和

文檔

db.orders.insertMany([
   { _id: 1, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-01"), price: 25, items: [ { sku: "oranges", qty: 5, price: 2.5 }, { sku: "apples", qty: 5, price: 2.5 } ], status: "A" },
   { _id: 2, cust_id: "Ant O. Knee", ord_date: new Date("2020-03-08"), price: 70, items: [ { sku: "oranges", qty: 8, price: 2.5 }, { sku: "chocolates", qty: 5, price: 10 } ], status: "A" },
   { _id: 3, cust_id: "Busby Bee", ord_date: new Date("2020-03-08"), price: 50, items: [ { sku: "oranges", qty: 10, price: 2.5 }, { sku: "pears", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 4, cust_id: "Busby Bee", ord_date: new Date("2020-03-18"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 5, cust_id: "Busby Bee", ord_date: new Date("2020-03-19"), price: 50, items: [ { sku: "chocolates", qty: 5, price: 10 } ], status: "A"},
   { _id: 6, cust_id: "Cam Elot", ord_date: new Date("2020-03-19"), price: 35, items: [ { sku: "carrots", qty: 10, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 7, cust_id: "Cam Elot", ord_date: new Date("2020-03-20"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 8, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 75, items: [ { sku: "chocolates", qty: 5, price: 10 }, { sku: "apples", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 9, cust_id: "Don Quis", ord_date: new Date("2020-03-20"), price: 55, items: [ { sku: "carrots", qty: 5, price: 1.0 }, { sku: "apples", qty: 10, price: 2.5 }, { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" },
   { _id: 10, cust_id: "Don Quis", ord_date: new Date("2020-03-23"), price: 25, items: [ { sku: "oranges", qty: 10, price: 2.5 } ], status: "A" }
])
  • map-reduce 操作

    • 定義 map 函數

      • 將 price字段 映射到 cust_id 字段,輸出 cust_id-price 鍵值對
      var mapFunction1 = function() {
         emit(this.cust_id, this.price);
      };
      
    • 定義 reduce 函數

      • 接收 map 函數返回的鍵值對列表,對每個鍵值對執行 reduce 函數

      • 將鍵作爲_id字段的值,執行結果作爲value字段的值,以文檔 { "_id": "<key>", "value": <執行結果> } 的形式返回

      var reduceFunction1 = function(keyCustId, valuesPrices) {
          // 具有一些 JS 目前沒有的接口
         return Array.sum(valuesPrices);
      };
      
    • 對集合中的所有文檔執行 map-reduce 操作

      • 將結果輸出到 map_reduce_example 集合中
      db.orders.mapReduce(
         mapFunction1,
         reduceFunction1,
         { out: "map_reduce_example" }
      )
      
  • 聚合操作

    db.orders.aggregate([
       { $group: { _id: "$cust_id", value: { $sum: "$price" } } },
       { $out: "agg_alternative_1" }
    ])
    

嵌套文檔

計算2020-03-01之後,每個sku(單品)的訂單數,總銷量,以及每個訂單平均銷量

以 sku 字段分組,計算每組中 qty 字段的總和,以及每組中_id字段不同的文檔的數量,最後計算平均銷量

  • map-reduce 操作

    • 定義map函數

      • 將包含 count 和 qty 字段的文檔,映射到 sku 字段,輸出鍵值對 key-value
      var mapFunction2 = function() {
          // 遍歷數組,對嵌套子文檔執行 emit 函數
          // 並不是每個元素都創建一個鍵值對,key已經存在時,只會將 value 放入對應的 value數組
          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);
          }
      };
      
    • 定義 reduce 函數

      • 接收 map 函數返回的鍵值對列表,對每個鍵值對執行 reduce 函數
      • 統計每個"值"中 count 字段和 qty 字段的值,輸出鍵值對 key-reduceVal
      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;
      };
      
    • 定義finalize 函數

      • 接收 reduce 函數返回的鍵值對列表,對每個鍵值對執行 finalize 函數
      • 在返回結果文檔中添加新字段 avg
      var finalizeFunction2 = function (key, reducedVal) {
        reducedVal.avg = reducedVal.qty/reducedVal.count;
        return reducedVal;
      };
      
    • 對集合中的所有文檔執行 map-reduce 操作

      • 將結果文檔合併到已存在的數據庫中
      db.orders.mapReduce(
         mapFunction2,
         reduceFunction2,
         {
           out: { merge: "map_reduce_example2" },
           query: { ord_date: { $gte: new Date("2020-03-01") } },
           finalize: finalizeFunction2
         }
       );
      
  • 聚合管道

    db.orders.aggregate( [
       { $match: { ord_date: { $gte: new Date("2020-03-01") } } },
       { $unwind: "$items" },
       { $group: { _id: "$items.sku", qty: { $sum: "$items.qty" }, orders_ids: { $addToSet: "$_id" } }  },
       { $project: { value: { count: { $size: "$orders_ids" }, qty: "$qty", avg: { $divide: [ "$qty", { $size: "$orders_ids" } ] } } } },
       { $merge: { into: "agg_alternative_3", on: "_id", whenMatched: "replace",  whenNotMatched: "insert" } }
    ] )
    
    • $match 階段,相當於 map-reduce 中的 query 參數
    • $unwind 階段,將嵌套數組拆分成單獨的文檔
    • $group 階段,相當於 map-reduce 中的 map 和 reduce 函數
    • $project 階段,相當於 map-reduce 中的 finalize 函數
    • $merge 階段,相等於 map-reduce 中的 out 參數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章