mondodb聚合框架

mondodb聚合框架

簡介

MongoDB的聚合框架,主要用來對集合中的文檔進行變換和組合,
從而對數據進行分析加以利用。

聚合框架的基本思想是:采用多個構建來創建一個管道,用于對一連串的文檔進行處理。

這些構建包括:篩選(filtering),投影(projecting),分組(grouping),排序(sorting),限制(limiting)和跳過(skipping)。

mongodb 聚合框架流程圖

使用聚合框架的方式

db.集合.aggregate(構建1,構建2,構建3...)
注意: 由于聚合的結果要返回到客戶端,因此聚合結果必須限制在16M以內,這是MomgoDB支持的最大響應消息的大小。

生產測試數據

        for(var i=0;i<100;i++){
                for(var j=0;j<4;j++){
                          db.scores.insert({"studentId":"s"+i,"course":"課程"+j,"score":Math.random()*100});
            }
         }

將產生400條數據,一個學生有四門課程的成績:
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc58b"), "studentId" : "s0", "course" : "課程0", "score" : 5.999703989474325 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc58c"), "studentId" : "s0", "course" : "課程1", "score" : 5.040777429297738 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc58d"), "studentId" : "s0", "course" : "課程2", "score" : 63.74926111710963 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc58e"), "studentId" : "s0", "course" : "課程3", "score" : 66.50186953829127 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc58f"), "studentId" : "s1", "course" : "課程0", "score" : 84.74255359132857 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc590"), "studentId" : "s1", "course" : "課程1", "score" : 85.7428949944855 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc591"), "studentId" : "s1", "course" : "課程2", "score" : 33.198227427842056 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc592"), "studentId" : "s1", "course" : "課程3", "score" : 5.346516072417174 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc593"), "studentId" : "s2", "course" : "課程0", "score" : 97.1040803312415 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc594"), "studentId" : "s2", "course" : "課程1", "score" : 20.47611488352149 }
   ...

管道操作符簡介

每個操作符接受一系列的文檔,對這些文檔做響應的處理,然后把轉換后的文檔作為文檔傳遞給下一個操作符。最后一個操作符會將結果返回。

管道操作符$match

用于文檔集合進行篩選,里面可以使用所有常規的操作符。通常會放置在管道最前面的位置,
理由如下:
1:快速將不需要的文檔過濾,減少后續操作的數據量。
2:在投影和分組之前做篩選,查詢可以使用索引

查找score大于90的三條數據

    db.scores.aggregate({"$match":{"score":{$gt:90}}},{"$limit":3})
    
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc593"), "studentId" : "s2", "course" : "課程0", "score" : 97.1040803312415 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc5a6"), "studentId" : "s6", "course" : "課程3", "score" : 95.37347295013626 }
    { "_id" : ObjectId("597ffbc1d8c0f25813bcc5ab"), "studentId" : "s8", "course" : "課程0", "score" : 90.49825247672626 }

管道操作符$project

用來從文檔中提取字段,可以指定包含和排除字段,也可以重命名字段。

只顯示studentId score

   db.scores.aggregate({"$match":{"score":{$gt:90}}},{"$project":{studentId:1,score:1,_id:0}},{"$limit":3})
   
{ "studentId" : "s2", "score" : 97.1040803312415 }
{ "studentId" : "s6", "score" : 95.37347295013626 }
{ "studentId" : "s8", "score" : 90.49825247672626 }

把 studentId 重命名為 sid $studentId 表示取值

    db.scores.aggregate({"$match":{"score":{$gt:90}}},{"$project":{"sid":"$studentId",score:1,_id:0}},{"$limit":3})


    { "score" : 97.1040803312415, "sid" : "s2" }
    { "score" : 95.37347295013626, "sid" : "s6" }
    { "score" : 90.49825247672626, "sid" : "s8" }

管道操作符還可以使用表達式,以滿足更復雜的需求。

支持的操作符和相應的語法:

  • 1:$add : [expr1[,expr2,...exprn]]

  • 2:$subtract:[expr1,expr2]

  • 3:$multiply:[expr1[,expr2,...exprn]]

  • 4:$divice:[expr1,expr2] 5:$mod:[expr1,expr2]

問題一:查詢出分數大于90分的人員分數加20分

db.scores.aggregate({"$match":{"score":{$gt:90}}},{"$project":{"sid":"$studentId","score":1,_id:0,"newScore":{$add:["$score",20]}}},{"$limit":3})

{ "score" : 97.1040803312415, "sid" : "s2", "newScore" : 117.1040803312415 }
{ "score" : 95.37347295013626, "sid" : "s6", "newScore" : 115.37347295013626 }
{ "score" : 90.49825247672626, "sid" : "s8", "newScore" : 110.49825247672626 }

管道操作符$project的日期表達式

合框架包含了一些用于提取日期信息的表達式,如下: $year、$month、$week、$dayOfMonth、$dayOfWeek、$dayOfYear、$hour、$minute、 $second 。

插入一條日期的文檔

 db.userdatas.insert({"date":new Date()})
 { "_id" : ObjectId("5983f5c88eec53fbcd56a7ca"), "date" : ISODate("2017-08-04T04:19:20.693Z") }

 db.userdatas.aggregate({"$match":{"_id":ObjectId("5983f5c88eec53fbcd56a7ca")}},
 {"$project":{"year":{"$year":"$date"},"month":{"$month":"$date"}}})

 { "_id" : ObjectId("5983f5c88eec53fbcd56a7ca"), "year" : 2017, "month" : 8 }

管道操作符$project的字符串表達式

  • 1:$substr : [expr,開始位置,要取的字節個數]

  • 2:$concat:[expr1[,expr2,...exprn]]

  • 3:$toLower:expr

  • 4:$toUpper:expr

db.scores.aggregate({"$project":{"sid":{"$concat":["$studentId","--"]}}},{"$limit":2})

{ "_id" : ObjectId("597ffbc1d8c0f25813bcc58b"), "sid" : "s0--" }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc58c"), "sid" : "s0--" }

管道操作符$project的邏輯表達式

1:$cmp:[expr1,expr2] :比較兩個表達式,0表示相等,正數前面的大,負數后面的大 。

2:$strcasecmp:[string1,string2] :比較兩個字符串,區分大小寫,只對由羅馬字符組成的字符串有效。

3:$eq、$ne、$gt、$gte、$lt、$lte :[expr1,expr2]

4:$and、$or、$not

5:$cond:[booleanExpr,trueExpr,falseExpr]:如果boolean表達式為true,返回true表達式,否則返回false表達式

6:$ifNull:[expr,otherExpr]:如果expr為null,返回otherExpr,否則返回expr

問題一:字段studentId 與字符"s3"比較大小

db.scores.aggregate({"$project":{"result":{"$strcasecmp":["$studentId","s3"]},"studentId":1}},{"$skip":8},{"$limit":12})


{ "_id" : ObjectId("597ffbc1d8c0f25813bcc593"), "studentId" : "s2", "result" : -1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc594"), "studentId" : "s2", "result" : -1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc595"), "studentId" : "s2", "result" : -1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc596"), "studentId" : "s2", "result" : -1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc597"), "studentId" : "s3", "result" : 0 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc598"), "studentId" : "s3", "result" : 0 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc599"), "studentId" : "s3", "result" : 0 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc59a"), "studentId" : "s3", "result" : 0 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc59b"), "studentId" : "s4", "result" : 1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc59c"), "studentId" : "s4", "result" : 1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc59d"), "studentId" : "s4", "result" : 1 }
{ "_id" : ObjectId("597ffbc1d8c0f25813bcc59e"), "studentId" : "s4", "result" : 1 }

$group 分組

用來將文檔依據特定字段的不同值進行分組。選定了分組字段后,就可以把這些字段傳遞給$group函數的“_id”字段。
  • 1:$sum:value :對于每個文檔,將value與計算結果相加

  • 2:$avg:value :返回每個分組的平均值

  • 3:$max:expr :返回分組內的最大值

  • 4:$min:expr :返回分組內的最小值

  • 5:$first:expr :返回分組的第一個值,忽略其他的值,一般只有排序后,明確知道數據順 序的時候,這個操作才有意義

  • 6:$last:expr :與上面一個相反,返回分組的最后一個值

  • 7:$addToSet:expr :如果當前數組中不包含expr,那就將它加入到數組中

  • 8:$push:expr:把expr加入到數組中

問題一:按照人進行分組

db.scores.aggregate({$group:{"_id":"$studentId"}}) 

問題二:算出每個人的平均分前十名的學生

db.scores.aggregate({$group:{"_id":"$studentId","avgscore":{"$avg":"$score"}}},{"$project":{"score":1,"avgscore":1}},{"$sort":{"avgscore":-1}},{"$limit":10})
{ "_id" : "s68", "avgscore" : 83.18088197080301 }
{ "_id" : "s29", "avgscore" : 81.47034353997472 }
{ "_id" : "s87", "avgscore" : 80.55368613639939 }
{ "_id" : "s14", "avgscore" : 78.15436971363175 }
{ "_id" : "s33", "avgscore" : 76.56089285481109 }
{ "_id" : "s35", "avgscore" : 74.60393122282694 }
{ "_id" : "s99", "avgscore" : 73.79357383668147 }
{ "_id" : "s6", "avgscore" : 72.401925493081 }
{ "_id" : "s89", "avgscore" : 71.40932193466372 }
{ "_id" : "s41", "avgscore" : 70.28421418013374 }

構件1:{$group:{"_id":"$studentId","avgscore":{"$avg":"$score"}}} 按照學生分組算出平均成績

構件2:{"$project":{"score":1,"avgscore":1}} :顯示score avgscore字段 _id 會自動顯示

構件3:{"$sort":{"avgscore":-1}}: 按照avgscore 降序排序

構件4:{"$limit":10} 顯示10條

問題三:算出每個人的總分最高的10個人。

    db.scores.aggregate({"$group":{"_id":"$studentId","count":{"$sum":"$score"}}},{"$sort":{"count":-1}},{"$limit":10})

    { "_id" : "s68", "count" : 332.72352788321206 }
    { "_id" : "s29", "count" : 325.8813741598989 }
    { "_id" : "s87", "count" : 322.21474454559757 }
    { "_id" : "s14", "count" : 312.617478854527 }
    { "_id" : "s33", "count" : 306.24357141924435 }
    { "_id" : "s35", "count" : 298.4157248913078 }
    { "_id" : "s99", "count" : 295.1742953467259 }
    { "_id" : "s6", "count" : 289.607701972324 }
    { "_id" : "s89", "count" : 285.6372877386549 }
    { "_id" : "s41", "count" : 281.136856720535 }

問題四:每個最高的得分的科目。

    db.scores.aggregate({"$group":{"_id":{"sid":"$studentId","course":"$course"},"max":{"$max":"$score"}}},{"$project":{"max":1,"course":1}},{"$sort":{"max":-1}},{"$limit":10})
{ "_id" : { "sid" : "s63", "course" : "課程1" }, "max" : 99.90599299936622 }
{ "_id" : { "sid" : "s35", "course" : "課程3" }, "max" : 99.30405860298266 }
{ "_id" : { "sid" : "s64", "course" : "課程0" }, "max" : 99.1537696673095 }
{ "_id" : { "sid" : "s50", "course" : "課程2" }, "max" : 98.52420500572934 }
{ "_id" : { "sid" : "s20", "course" : "課程0" }, "max" : 98.28681013083249 }
{ "_id" : { "sid" : "s40", "course" : "課程0" }, "max" : 98.2529822556756 }
{ "_id" : { "sid" : "s30", "course" : "課程2" }, "max" : 98.2195611010153 }
{ "_id" : { "sid" : "s65", "course" : "課程3" }, "max" : 97.93186806691125 }
{ "_id" : { "sid" : "s10", "course" : "課程1" }, "max" : 97.87209639127002 }
{ "_id" : { "sid" : "s23", "course" : "課程3" }, "max" : 97.36489703312259 }

拆分命令:$unwind

用來把數組中的每個值拆分成為單獨的文檔。

問題一:把score 字段數組拆分。

    db.userdatas.find({"name":"u4"})
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : [ 7, 4, 2, 0 ] }

    db.userdatas.aggregate({"$match":{"name":"u4"}},{"$unwind":"$score"})
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : 7 }
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : 4 }
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : 2 }
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : 0 }

構件1: {"$match":{"name":"u4"}} 查詢出 name=u4 的文檔,然后把結果給 構件2.

構件2:{"$unwind":"$score"} 把score 拆分成一個個獨立的文檔。

排序命令:$sort

可以根據任何字段進行排序,與普通查詢中的語法相同。如果要對大量的文檔進行排序。
強烈建議在管道的第一個階段進行排序,這時可以使用索引。

問題一:按照年齡字段排序

    db.userdatas.find()
{ "_id" : ObjectId("59789a56bc629e73c4f09e1c"), "name" : "wang wu", "age" : 45 }
{ "_id" : ObjectId("59789a74bc629e73c4f09e1e"), "name" : "wang wu", "age" : 8 }
{ "_id" : ObjectId("59789ac0bc629e73c4f09e20"), "name" : "wang wu", "age" : 33 }
{ "_id" : ObjectId("597f357a09c84cf58880e40e"), "name" : "u1", "age" : 37 }
{ "_id" : ObjectId("597f357a09c84cf58880e40f"), "name" : "u1", "age" : 37 }
{ "_id" : ObjectId("597f357a09c84cf58880e410"), "name" : "u5", "age" : 78 }
{ "_id" : ObjectId("597f357a09c84cf58880e412"), "name" : "u3", "age" : 32 }
{ "_id" : ObjectId("597f357a09c84cf58880e411"), "name" : "u4", "age" : 30, "score" : [ 7, 4, 2, 0 ] }
{ "_id" : ObjectId("597fcc0f411f2b2fd30d0b3f"), "age" : 20, "score" : [ 7, 4, 2, 0, 10, 9, 8, 7 ], "name" : "lihao" }
{ "_id" : ObjectId("597f357a09c84cf58880e413"), "name" : "u2", "age" : 33, "wendang" : { "yw" : 80, "xw" : 90 } }
{ "_id" : ObjectId("5983f5c88eec53fbcd56a7ca"), "date" : ISODate("2017-08-04T04:19:20.693Z") }

    db.userdatas.aggregate({"$sort":{"age":-1}},{"$match":{"age":{"$exists":1}}},{"$project":{"name":1,"age":1,"_id":0}})
    
{ "name" : "u5", "age" : 78 }
{ "name" : "wang wu", "age" : 45 }
{ "name" : "u1", "age" : 37 }
{ "name" : "u1", "age" : 37 }
{ "name" : "wang wu", "age" : 33 }
{ "name" : "u2", "age" : 33 }
{ "name" : "u3", "age" : 32 }
{ "name" : "u4", "age" : 30 }
{ "age" : 20, "name" : "lihao" }
{ "name" : "wang wu", "age" : 8 }

構件1:{"$sort":{"age":-1}} 按照age字段降序,把數據給構件2.

構件2:{"$match":{"age":{"$exists":1}}} 篩選出 age存在的字段,然后把數據給構件3,

構件3:{"$project":{"name":1,"age":1,"_id":0}} 投影 只顯示 name, age 字段。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,830評論 0 11
  • 50個常用的sql語句Student(S#,Sname,Sage,Ssex) 學生表Course(C#,Cname...
    哈哈海閱讀 1,247評論 0 7
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 靠璞刺青閱讀 196評論 1 4
  • 我和你,沒有故事,沒有死生契闊。我認識你,牽過你的手,擁抱著你,吻過你的淚。我懂你,愛你,深情如咖啡,濃烈似白酒,...
    茶娃娃閱讀 268評論 0 0