mongodb學習記錄之四:聚合
group這個一直是迷迷糊糊的,上網找了好多例子,慢慢分析,慢慢消化。
下面兩個例子都是mongodb權威指南一本書上給的。
下面使用一個例子來消化group,先準備資料:
db.blog.insert({title:"J2EE實戰",author:"li",day:"2012-12-12",tags:["java","J2EE","struts2","spring","hibernate"]}); db.blog.insert({title:"輕量級J2EE開發",author:"ligang",day:"2012-12-14",tags:["java","J2EE","struts2","spring","hibernate"]}); db.blog.insert({title:"瘋狂Java",author:"May",day:"2012-12-16",tags:["java","J2SE"]}); db.blog.insert({title:"android開發例項",author:"WenDy",day:"2012-12-18",tags:["java","android","J2ME","移動開發"]}); db.blog.insert({title:"MongoDB權威指南",author:"coolcao",day:"2012-12-16",tags:["mongdb","nosql","資料庫"]}); db.blog.insert({title:"srping-data-mongo",author:"lucy",day:"2012-12-16",tags:["java","spring-data","mongdb","資料庫"]}); db.blog.insert({title:"struts2權威指南",author:"jack",day:"2012-12-12",tags:["java","J2EE","struts2","MVC"]}); db.blog.insert({title:"springMVC",author:"cate",day:"2012-12-18",tags:["java","J2EE","spring","MVC","springMVC"]}); db.blog.insert({title:"Oracle",author:"dog",day:"2012-12-12",tags:["資料庫","Oracle"]}); db.blog.insert({title:"mysql",author:"zhu",day:"2012-12-14",tags:["資料庫","mysql"]});
上面是一組部落格的資料資料,其中tags表示的是一篇部落格的相關標籤。問題是要按照時間統計出每天的部落格中,提到最多的標籤tags是哪些
這裡就要使用group
db.blog.group({ key:{"day":true}, initial:{tags:{}}, $reduce:function(doc,prev){ for(i in doc.tags){ if(doc.tags[i] in prev.tags){ prev.tags[doc.tags[i]]++; }else{ prev.tags[doc.tags[i]]=1; } } } }); 或: db.runCommand({ group:{ ns:"blog", key:{day:true}, initial:{tags:{}}, $reduce:function(doc,prev){ for(i in doc.tags){ if(doc.tags[i] in prev.tags){ prev.tags[doc.tags[i]]++; }else{ prev.tags[doc.tags[i]]=1; } } } } });
結果:
/* 0 */ { "0" : { "day" : "2012-12-12", "tags" : { "java" : 2, "J2EE" : 2, "struts2" : 2, "spring" : 1, "hibernate" : 1, "MVC" : 1, "資料庫" : 1, "Oracle" : 1 } }, "1" : { "day" : "2012-12-14", "tags" : { "java" : 1, "J2EE" : 1, "struts2" : 1, "spring" : 1, "hibernate" : 1, "資料庫" : 1, "mysql" : 1 } }, "2" : { "day" : "2012-12-16", "tags" : { "java" : 2, "J2SE" : 1, "mongdb" : 2, "nosql" : 1, "資料庫" : 2, "spring-data" : 1 } }, "3" : { "day" : "2012-12-18", "tags" : { "java" : 2, "android" : 1, "J2ME" : 1, "移動開發" : 1, "J2EE" : 1, "spring" : 1, "MVC" : 1, "springMVC" : 1 } } }
從例子中可以看出,例子裡的查詢語句中,統計了每天的每個tags的數量,按日期分組輸出。
key即要分組的鍵,我們要按日期輸出,那麼分組的鍵就是day
initial,初始化的文件。group最終的輸出是以文件的形式,在計算的過程中會採用“疊加”的方式進行統計。initial初始化的便是一個空的文件。用來“疊加”的最初的文件。
$reduce,這裡是一個函式。在mongo中,使用的是js函式操作資料,因此一些複雜的操作,都是可以使用自定義函式來實現。$reduce便是分組統計時的邏輯實現函式。函式的兩個引數doc是每次遍歷時的文件,prev是前一次的文件。
拿上面的例子來講,tags的值是一個數組,for迴圈遍歷每個文件的tags值,如果此次遍歷中的tags值在前一次遍歷中出現過,那麼前一次遍歷的tags要加1,如果在前一次遍歷中沒有出現,那麼把這個沒有的tag放入文件,初始值為1。
如此遍歷結束,便統計出了每天的部落格的每個標籤的出現次數。可能這裡有點繞,對照著上面的group函式再思量一下。
返回的文件即最後的prev文件,其中的鍵,其實是由key和initial初始化的文件組成的。上面的例子中,key是day,initial初始化的陣列是tags:{},因此最終輸出的文件包含day,tags鍵
可是這樣並沒有達到我們的要求,我們只是想要每天出現次數最多的標籤,看看相關部落格哪方面是最熱門的
db.blog.group({
key:{"day":true},
initial:{tags:{}},
$reduce:function(doc,prev){
for(i in doc.tags){
if(doc.tags[i] in prev.tags){
prev.tags[doc.tags[i]]++;
}else{
prev.tags[doc.tags[i]]=1;
}
}
},
finalize:function(prev){
var mostPopular = 0 ;
for(i in prev.tags){
if(prev.tags[i]>mostPopular){
prev.tag = i;
mostPopular = prev.tags[i];
}
}
delete prev.tags;
}
});
或:
db.blog.runCommand({
group:{
ns:"blog",
key:{day:true},
initial:{tags:{}},
$reduce:function(doc,prev){
for(i in doc.tags){
if(doc.tags[i] in prev.tags){
prev.tags[doc.tags[i]]++;
}else{
prev.tags[doc.tags[i]] = 1;
}
}
},
finalize:function(prev){
var mostPopular = 0;
for(i in prev.tags){
if(prev.tags[i]>mostPopular){
prev.tag = i;
mostPopular = prev.tags[i];
}
}
delete prev.tags;
}
}
});
結果:
/* 0 */
{
"retval" : [
{
"day" : "2012-12-12",
"tag" : "java"
},
{
"day" : "2012-12-14",
"tag" : "java"
},
{
"day" : "2012-12-16",
"tag" : "java"
},
{
"day" : "2012-12-18",
"tag" : "java"
}
],
"count" : 10,
"keys" : 4,
"ok" : 1
}
finalize便是最後的過濾函式。我們將每個標籤按天統計出來後,再遍歷統計結果,拿出出現次數最多的標籤留下。如上面的結果
上面例子是我參照之前轉的一篇部落格中的例子,也和mongodb權威指南中的例子差不多,大家可以參考一下,自己想點別的例子實踐一下。
下面是mongodb權威指南上的一個例子
db.stocks.insert({day:"2012-12-12",time:"2012-12-12 12:00:00",price:4.23});
db.stocks.insert({day:"2012-12-12",time:"2012-12-12 13:00:00",price:4.33});
db.stocks.insert({day:"2012-12-12",time:"2012-12-12 14:00:00",price:4.26});
db.stocks.insert({day:"2012-12-12",time:"2012-12-12 15:00:00",price:4.02});
db.stocks.insert({day:"2012-12-12",time:"2012-12-12 16:00:00",price:4.18});
db.stocks.insert({day:"2012-12-13",time:"2012-12-13 12:00:00",price:4.19});
db.stocks.insert({day:"2012-12-13",time:"2012-12-13 13:00:00",price:4.04});
db.stocks.insert({day:"2012-12-13",time:"2012-12-13 14:00:00",price:4.35});
db.stocks.insert({day:"2012-12-13",time:"2012-12-13 15:00:00",price:4.31});
db.stocks.insert({day:"2012-12-13",time:"2012-12-13 16:00:00",price:4.08});
db.stocks.insert({day:"2012-12-14",time:"2012-12-14 12:00:00",price:4.08});
db.stocks.insert({day:"2012-12-14",time:"2012-12-14 13:00:00",price:4.12});
db.stocks.insert({day:"2012-12-14",time:"2012-12-14 14:00:00",price:4.24});
db.stocks.insert({day:"2012-12-14",time:"2012-12-14 15:00:00",price:4.09});
db.stocks.insert({day:"2012-12-14",time:"2012-12-14 16:00:00",price:4.16});
每個文件中,day是指的哪一天,time是具體每天的哪個點,price指的是股票的價格。
我們也要按照天分組顯示每天中股票的最高價格,這裡不是統計數量了,而是找出最大值。
db.stocks.group({
key:{day:true},
initial:{time:"",price:0},
$reduce:function(doc,prev){
for(i in doc.price){
if(doc.price>prev.price){
prev.price = doc.price;
prev.time = doc.time;
}
}
}
});
或者:
db.runCommand({
group:{
ns:"stocks",
key:{day:true},
initial:{time:"",price:0},
$reduce:function(doc,prev){
for(i in doc.price){
if(doc.price>prev.price){
prev.price = doc.price;
prev.time = doc.time;
}
}
}
}
});
結果:
{
"0" : {
"day" : "2012-12-12",
"time" : "2012-12-12 13:00:00",
"price" : 4.33
},
"1" : {
"day" : "2012-12-13",
"time" : "2012-12-13 14:00:00",
"price" : 4.35
},
"2" : {
"day" : "2012-12-14",
"time" : "2012-12-14 14:00:00",
"price" : 4.24
}
}
其實這個例子比上一個例子要簡單點,雖然兩個例子都挺簡單的,第一個例子先是統計,最後選擇最大值,第二個例子直接選擇最大值即可,因此沒有使用finalize最後過濾器這個引數。
注意:如上兩個例子中都沒有使用$keyf,cond兩個引數,$keyf,cond,finalize這三個引數都是可選的。
$keyf和cond兩個引數在以後學習中繼續補充
注意:db.runCommand()和db.collection.group()兩種方法返回的資料有點小區別,上面例子中並沒有做詳細區分。兩種方法的區別會慢慢學習。