1. 程式人生 > >mongodb學習記錄之四:聚合

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()兩種方法返回的資料有點小區別,上面例子中並沒有做詳細區分。兩種方法的區別會慢慢學習。