MongoDB系列六(聚合).

1、概念

    使用聚合框架能夠對集合中的文檔進行變換和組合。基本上,能夠用多個構件建立一個管道(pipeline),用於對一連串的文檔進行處理。這些構件包括篩選(filtering)、投射(projecting)、分組(grouping)、排序(sorting)、限制(limiting)和跳過(skipping)。mongodb

2、聚合函數

db.driverLocation.aggregate(
    {"$match":{"areaCode":"350203"}},
    {"$project":{"driverUuid":1,"uploadTime":1,"positionType":1}},
    {"$group":{"_id":{"driverUuid":"$driverUuid","positionType":"$positionType"},"uploadTime":{"$first":{"$year":"$uploadTime"}},"count":{"$sum":1}}},
    {"$sort":{"count":-1}},
    {"$limit":100},
    {"$skip":50}
)

     管道操做符是按照書寫的順序依次執行的,每一個操做符都會接受一連串的文檔,對這些文檔作一些類型轉換,最後將轉換後的文檔做爲結果傳遞給下一個操做符(對於最後一個管道操做符,是將結果返回給客戶端),稱爲流式工做方式。數組

    大部分操做符的工做方式都是流式的,只要有新文檔進入,就能夠對新文檔進行處理,可是"$group" 和 "$sort" 必需要等收到全部的文檔以後,才能對文檔進行分組排序,而後才能將各個分組發送給管道中的下一個操做符。這意味着,在分片的狀況下,"$group" 或 "$sort"會先在每一個分片上執行,而後各個分片上的分組結果會被髮送到mongos再進行最後的統一分組,剩餘的管道工做也都是在mongos(而不是在分片)上運行的。app

    不一樣的管道操做符能夠按任意順序組合在一塊兒使用,並且能夠被重複任意屢次。例如,能夠先作"$match",而後作"$group",而後再作"$match"(與以前的"$match"匹配不一樣的查詢條件)。框架

    $fieldname"語法是爲了在聚合框架中引用fieldname字段。ide

  • 篩選(filtering)—> $match

    用於對文檔集合進行篩選,以後就能夠在篩選獲得的文檔子集上作聚合。例如,若是想對Oregon(俄勒岡州,簡寫爲OR)的用戶作統計,就可使用{$match : {"state" :"OR"}}。"$match"可使用全部常規的查詢操做符("$gt"、"$lt"、"$in"等)。有一個例外須要注意:不能在"$match"中使用地理空間操做符。
    一般,在實際使用中應該儘量將"$match"放在管道的前面位置。這樣作有兩個好處:一是能夠快速將不須要的文檔過濾掉,以減小管道的工做量;二是若是在投射和分組以前執行"$match",查詢可使用索引。函數

  • 投射(projecting)—> $project

    這個語法與查詢中的字段選擇器比較像:能夠經過指定 {"fieldname" : 1} 選擇須要投射的字段,或者經過指定 { "fieldname":0 } 排除不須要的字段。執行完這個"$project"操做以後,結果集中的每一個文檔都會以{"_id" : id, "fieldname" :"xxx"}這樣的形式表示。這些結果只會在內存中存在,不會被寫入磁盤。ui

    還能夠對字段進行重命名:db.users.aggregate({"$project" : {"userId" : "$_id", "_id" : 0}}),在對字段進行重命名時,MongoDB並不會記錄字段的歷史名稱。編碼

  • 分組(grouping)—> $group

     若是選定了須要進行分組的字段,就能夠將選定的字段傳遞給"$group"函數的"_id"字段。對於上面的例子:咱們選擇了driverUuid 和 positionType 看成咱們分組的條件(固然只選擇一個字段也是能夠的)。分組事後,文檔的 driverUuid 和 positionType 組成的對象就變成了文檔的惟一標識(_id)。spa

  "count":{"$sum":1} 是爲分組內每一個文檔的"count"字段加1。注意,新加入的文檔中並不會有"count"字段;這"$group"建立的一個新字段。  code

  • 排序(sorting)—> $sort

   排序方向能夠是1(升序)和 -1(降序)。  

   能夠根據任何字段(或者多個字段)進行排序,與在普通查詢中的語法相同。若是要對大量的文檔進行排序,強烈建議在管道的第一階段進行排序,這時的排序操做可使用索引。不然,排序過程就會比較慢,並且會佔用大量內存。

  • 限制(limiting)—> $limit

   $limit會接受一個數字n,返回結果集中的前n個文檔。

  • 跳過(skipping)—> $skip

   $skip也是接受一個數字n,丟棄結果集中的前n個文檔,將剩餘文檔做爲結果返回。在「普通」查詢中,若是須要跳過大量的數據,那麼這個操做符的效率會很低。在聚合中也是如此,由於它必需要先匹配到全部須要跳過的文檔,而後再將這些文檔丟棄。

  • 拆分(unwind)—> $unwind

   能夠將數組中的每個值拆分爲單獨的文檔。

   例如文檔:{ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] }

   聚合運算:db.inventory.aggregate( [ { $unwind : "$sizes" } ] )

   結果:

{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }

Spring Data MongoDB 中使用聚合函數:

    /**
     * db.driverLocation.aggregate(
     *     {"$match":{"areaCode":"350203"}},
     *     {"$project":{"driverUuid":1,"uploadTime":1,"positionType":1}},
     *     {"$group":{"_id":{"driverUuid":"$driverUuid","positionType":"$positionType"},"uploadTime":{"$first":{"$year":"$uploadTime"}},"count":{"$sum":1}}},
     *     {"$sort":{"count":-1}},
     *     {"$limit":100},
     *     {"$skip":50}
     * )
     */
    @Test
    public void test04(){
        //match
        Criteria criteria = Criteria.where("350203").is("350203");
        AggregationOperation matchOperation = Aggregation.match(criteria);
        //project
        AggregationOperation projectionOperation = Aggregation.project("driverUuid", "uploadTime", "positionType");
        //group
        AggregationOperation groupOperation = Aggregation.group("driverUuid", "positionType")
                .first(DateOperators.dateOf("uploadTime").year()).as("uploadTime")
                .count().as("count");
        //sort
        Sort sort = new Sort(Sort.Direction.DESC, "count");
        AggregationOperation sortOperation = Aggregation.sort(sort);
        //limit
        AggregationOperation limitOperation = Aggregation.limit(100L);
        //skip
        AggregationOperation skipOperation = Aggregation.skip(50L);

        Aggregation aggregation = Aggregation.newAggregation(matchOperation, projectionOperation, groupOperation, sortOperation, limitOperation, skipOperation);
        AggregationResults<Object> driverLocation = mongoOperations.aggregate(aggregation, "driverLocation", Object.class);
        List<Object> mappedResults = driverLocation.getMappedResults();

    }

3、聚合管道操做符

    MongoDB提供了不少的操做符用來文檔聚合後字段間的運算或者分組內的統計,好比上文提到的$sum、$first、$year 等。MongoDB提供了包括分組操做符、數學操做符、日期操做符、字符串表達式 等等 一系列的操做符...

  • 分組操做符

相似 SQL中分組後的操做,只適用於分組後的統計工做,不適用於單個文檔。

  1. {"$sum" : value}  對於分組中的每個文檔,將value與計算結果相加。
  2. {"$avg" : value} 返回每一個分組的平均值
  3. {"$max" : expr} 返回分組內的最大值。
  4. {"$min" : expr} 返回分組內的最小值。
  5. {"$first" : expr} 返回分組的第一個值,忽略後面全部值。只有排序以後,明確知道數據順序時這個操做纔有意義。
  6. {"$last" : expr} 與"$first"相反,返回分組的最後一個值。
  7. {"$addToSet" : expr} 針對數組字段, 若是當前數組中不包含expr ,那就將它添加到數組中。在返回結果集中,每一個元素最多隻出現一次,並且元素的順序是不肯定的。
  8. {"$push" : expr} 針對數組字段,無論expr是什麼值,都將它添加到數組中。返回包含全部值的數組。
  • 數學操做符

適用於單個文檔的運算。

  1. {"$add" : [expr1[, expr2, ..., exprN]]} 這個操做符接受一個或多個表達式做爲參數,將這些表達式相加。
  2. {"$subtract" : [expr1, expr2]} 接受兩個表達式做爲參數,用第一個表達式減去第二個表達式做爲結果。
  3. {"$multiply" : [expr1[, expr2, ..., exprN]]} 接受一個或者多個表達式,而且將它們相乘。
  4. {"$divide" : [expr1, expr2]} 接受兩個表達式,用第一個表達式除以第二個表達式的商做爲結果。
  5. {"$mod" : [expr1, expr2]} 接受兩個表達式,將第一個表達式除以第二個表達式獲得的餘數做爲結果。
  • 字符串表達式

適用於單個文檔的運算。

  1. {$substr" : [expr, startOffset, numToReturn]} 其中第一個參數expr必須是個字符串,這個操做會截取這個字符串的子串(從第startOffset字節開始的numToReturn字節,注意,是字節,不是字符。在多字節編碼中尤爲要注意這一點)expr必須是字符串。
  2. {"$concat" : [expr1[, expr2, ..., exprN]]} 將給定的表達式(或者字符串)鏈接在一塊兒做爲返回結果。
  3. {"$toLower" : expr} 參數expr必須是個字符串值,這個操做返回expr的小寫形式。
  4. {"$toUpper" : expr} 參數expr必須是個字符串值,這個操做返回expr的大寫形式。
  • 邏輯表達式

適用於單個文檔的運算,經過這些操做符,就能夠在聚合中使用更復雜的邏輯,能夠對不一樣數據執行不一樣的代碼,獲得不一樣的結果。

  1. {$cmp" : [expr1, expr2]} 比較expr1和expr2。若是expr1等於expr2,返回0;若是expr1 < expr2,返回一個負數;若是expr1 > expr2,返回一個正數。
  2. {"$strcasecmp" : [string1, string2]} 比較string1和string2,區分大小寫。只對 ASCII 組成的字符串有效。
  3. {"$eq"/"$ne"/"$gt"/"$gte"/"$lt"/"$lte" : [expr1, expr2]} 對expr1和expr2執行相應的比較操做,返回比較的結果(true或false)。
  4. {"$and" : [expr1[, expr2, ..., exprN]]} 若是全部表達式的值都是true,那就返回true,不然返回false。
  5. {"$or" : [expr1[, expr2, ..., exprN]]} 只要有任意表達式的值爲true,就返回true,不然返回false。
  6. {"$not" : expr} 對expr取反。
  7. {"$cond" : [booleanExpr, trueExpr, falseExpr]} 若是booleanExpr的值是true,那就返回trueExpr,不然返回falseExpr。
  8. {"$ifNull" : [expr, replacementExpr]} 若是expr是null,返回replacementExpr,不然返回expr。
  • 日期表達式

適用於單個文檔的運算,只能對日期類型的字段進行日期操做,不能對非日期類型字段作日期操做。

  1. {$year: "$date" } 返回日期的年份部分
  2. {$month: "$date" } 返回日期的月份部分
  3. {$dayOfMonth: "$date" } 返回日期的天部分
  4. {$hour: "$date" } 返回日期的小時部分
  5. {$minute: "$date" } 返回日期的分鐘部分
  6. {$second: "$date" } 返回日期的秒部分
  7. {$millisecond: "$date" } 返回日期的毫秒部分
  8. {$dayOfYear: "$date" } 一年中的第幾天
  9. {$dayOfWeek: "$date" } 一週中的第幾天,between 1 (Sunday) and 7 (Saturday).
  10. {$week: "$date" } 以0到53之間的數字返回一年中日期的週數。周從星期日開始,第一週從一年中的第一個星期天開始。一年中第一個星期日以前的日子是在第0周。

可參考:https://docs.mongodb.com/manual/reference/operator/aggregation/

4、結語

    應該儘可能在管道的開始階段(執行"$project"、"$group"或者"$unwind"操做以前)就將盡量多的文檔和字段過濾掉。管道若是不是直接從原先的集合中使用數據,那就沒法在篩選和排序中使用索引。若是可能,聚合管道會嘗試對操做進行排序,以便可以有效使用索引。

    MongoDB不容許單一的聚合操做佔用過多的系統內存:若是MongoDB發現某個聚合操做佔用了20%以上的內存,這個操做就會直接輸出錯誤。容許將輸出結果利用管道放入一個集合中是爲了方便之後使用(這樣能夠將所需的內存減至最小)。

    這篇文章主要摘錄自《MongoDB權威指南第二版》,Mongo系列的最後一篇文章了,最近學MongoDB學得頭都有點大了,準備換個方向學學了...共勉!

相關文章
相關標籤/搜索