一、group與sum的概念
1.知識儲備:聚合與管道
1.1 MongoDB 聚合:
MongoDB 中聚合(aggregate)主要用於處理數據(諸如統計平均值,求和等),並返回計算后的數據結果。
有點類似 SQL 語句中的 count(*)。
介紹一下聚合的中的一些表達式方法:
表達式 | 描述 | 實例 |
---|---|---|
$sum | 計算總和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) |
$avg | 計算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) |
$min | 獲取集合中所有文檔對應值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) |
$max | 獲取集合中所有文檔對應值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) |
$push | 在結果文檔中插入值到一個數組中。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) |
$addToSet | 在結果文檔中插入值到一個數組中,但不創建副本。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) |
$first | 根據資源文檔的排序獲取第一個文檔數據。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) |
$last | 根據資源文檔的排序獲取最后一個文檔數據 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) |
1.2 MongoDB 管道:
管道在Unix和Linux中一般用於將當前命令的輸出結果作為下一個命令的參數。
MongoDB的聚合管道將MongoDB文檔在一個管道處理完畢后將結果傳遞給下一個管道處理。管道操作是可以重復的。
表達式:處理輸入文檔並輸出。表達式是無狀態的,只能用於計算當前聚合管道的文檔,不能處理其它的文檔。
這里我們介紹一下聚合框架中常用的幾個操作:
- $project:修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。
- $match:用於過濾數據,只輸出符合條件的文檔。$match使用MongoDB的標准查詢操作。
- $limit:用來限制MongoDB聚合管道返回的文檔數。
- $skip:在聚合管道中跳過指定數量的文檔,並返回余下的文檔。
- $unwind:將文檔中的某一個數組類型字段拆分成多條,每條包含數組中的一個值。
- $group:將集合中的文檔分組,可用於統計結果。
- $sort:將輸入文檔排序后輸出。
- $geoNear:輸出接近某一地理位置的有序文檔。
1.3 聚合和管道的合作:
例子:這段將會把文章的分數在70到90之間的所有文章,然后將符合條件的記錄送到下一階段$group管道操作符進行處理。(所以理論上你可以定義一個聚合,在聚合下使用無數個管道)
group將會使用id進行分組,然后sum來計算每一個相同_id的總數並賦值給count。最后打印count的值就是所有符合條件的文章的數目了。
db.articles.aggregate( [ { $match : { score : { $gt : 70, $lte : 90 } } }, { $group: { _id: null, count: { $sum: 1 } } } ] );
此時看到此處,有的人可能會有疑問:
1.group使用id進行分組,那么id怎么為null;
2.sum進行計算,為什么給sum后面賦值個1;
別急,往下看:
2.正文:
group上面已解釋是一種管道,將符合條件的進行分組,sum是聚合的計算方法。
sum可以在下面的管道或者方法中使用:
sum后面通常跟得是一個field(翻譯為領域,我把它理解為變量領域,可能不太恰當,但是我找不到其它詞去形容它):
1.如果這個變量領域是數值類型的,那么它會根據變量的數值進行計算;
2.如果這個變量領域有數值也有非數值類型的,它會根據那個數值類型的進行計算;
3.如果這個變量領域(不管是不是數值類型),只要他在表中沒出現,那么它會返回0;
4.如果這個變量領域是非數值類型的,那么它會返回0;
看了sum后面的field的介紹,有的人可能馬上反應過來,但是,有的人還可能一臉悶逼,那么我繼續解釋:
1.由於在group中使用_id進行分組,那么mogodb的group管道就會根據_id的具體值進行分組,但是當我們傳入_id為:null時,mongodb的group此時就不會根據_id的具體值進行分組,它會把所有符合條件的記錄看成一個整體,此時group取出的將是整個符合條件的所有記錄。
2.由於sum后面跟的是1的值,那么sum在遍歷每一個group組合的成員時,或者說sum在遍歷變量領域的每一個成員的時候,由於這個變量領域里的值都是1,它每遍歷到一個變量(表里的每條符合條件的記錄)都會進行加1操作,此時由於group本身只有一個,就是一整個表中符合條件的所有記錄,那么sum遍歷整個表的每一條符合條件的記錄時就會進行加1操作,隨后count保存的就是所有符合條件的值。
所以通常情況下,我可以使用group+sum的組合寫法,將數據庫每條記錄進行分類:
我有這樣一個數據庫:其中除了flag是布爾類型,agree是number類型,其它的都是字符串類型
現在我想做的是,如何計算每一條記錄中的agree使他們相加起來,再返回給我:
例子:下面這段代碼的執行過程:
1.使用group將每一個_id進行分組。
2.計算每一個agree的總和,由於我的_id是不重復的,所以sum的作用其實沒有體現出來,不過當你的表里有多個記錄是同一個id值,那么sum將會把每一個相同_id下的agree值進行計算,然后打印。
3.把計算后的每一個_id的agree總和作為commentAgreesCount的值。
4.打印輸出。
db.article_comments.aggregate( [{ $group : { _id:"$_id", commentAgreesCount: { $sum: "$agree" }} }] )
運行結果: { "_id" : ObjectId("606a70ac298cbe2978047810"), "commentAgreesCount" : 1 } { "_id" : ObjectId("606a60ba3878e73b78a9f5f0"), "commentAgreesCount" : 1 } { "_id" : ObjectId("606a7694298cbe2978047814"), "commentAgreesCount" : 0 } { "_id" : ObjectId("606a5ce13878e73b78a9f5ec"), "commentAgreesCount" : 1 }
但是我的本意並不是想獲得每一個分組后的結果,我是要獲取所有的記錄的agree總和。
所以解決方法:
1.把id定義為null,mongodb內部就會不區分_id,即把所有的_id進行計算,然后輸出結果
db.article_comments.aggregate( [{ $group : { _id:null, commentAgreesCount: { $sum: "$agree" }} }] )
運行結果: { "_id" : null, "commentAgreesCount" : 3 }
但是sum方法只能計算數值類型的,如果是字符串類型的,它就會默認把值設置為0。這個我上面也有貼官網的圖,也有進行相應的解釋。
例子:
db.article_comments.aggregate( [{$group : { _id:null, commentAgreesCount: { $sum: "$topic_id" } } }] )
結果:
{ "_id" : null, "commentAgreesCount" : 0 }
3.后話:
如果你要查詢的是非數值類型的總和,打比方:我的agree設置成了String類型,但是我想獲得每一條記錄中agree加起來的總和。
又或者是我有一個數據庫,里面保存着這個月賣出去的所有商品,所有商品的id是字符類型的,我想統計所有商品的賣出情況,比如id:1的所有商品,因為id:1的商品一個月內可能賣出很多次。id:2的所有商品,因為id:2的商品一個月內也可能賣出很多次 ……以此類推,然后我想計算所有id相同的商品賣出去的情況,並使用sum進行計算所有相同id商品的價格*件數=每件商品的營業額
此時再假設你的商品價格也是字符串類型的,那么此時就無法用上面那個sum方法計算了,此時解決方法也有,就是根據數據庫查詢到的所有記錄,再進行foreach循環遍歷一下所有商品的價格字符,再把他們轉換為數值類型的進行計算。
所以數據庫的方法並不是萬能的,有時候你無法根據數據庫進行查詢,獲取想要的數據類型時,那么只能將數據庫獲得的冗余數據,使用其他語言再進行數據處理。