關於MongoDB的group用法


之前在看Mongo的書時,看到了聚合這章。其中談到了group這個功能,其實正如書中所說,MongoDB中的group和SQL中的group by是很相似的,但我自我分析,可能由於Mongo中的group的使用形式不同,而且使用的是js語法,所以導致咋一看上去不明白這個group怎么用。下面通過具體的一個例子來詳細說明Mongo的group用法。

我們平常所用的博客,每天會有很多人發博客,每篇文章中都有多個標簽,現在要找出每天最熱點的標簽。首先,我們可以按天分組,將每天每一標簽的計數給統計出來。

我們可以簡單地假設集合中文檔的結構如下:

1 {“title” : “java sun”, “author” : “jk”, “day” : “2012-12-14”, “tags” : [“java”, “nosql”, “spring”]}
2 
3 {“title” : “SSH2的整合”, “author” : “cj”, “day” : “2012-5-10”, “tags” : [“struts2”, “hibernate”, “spring”]}
4 
5 {“title” : “C#的高級用法”, “author” : “zt”, “day” : “2012-4-3”, “tags” : [“C#”, “SQL”]}
6 
7 {“title” : “PHP Mongo”, “author” : “lx”, “day” : “2012-12-14”, “tags” : [“PHP”, “nosql”, “mongo”]}
8 
9

Mongo代碼如下:

 1 >db.posts.group({
 2 
 3 … “key” : {“day” : true},
 4 
 5 … “initial” : {“tags” : {}},
 6 
 7 … “$reduce” : function (doc, prev) {
 8 
 9for (i in doc.tags) {
10 
11if (doc.tags[i] in prev.tags) {
12 
13 …                       prev.tags[doc.tags[i]]++;
14 
15 …              }
16 
17else
18 
19 …              {
20 
21 …                       prev.tags[doc.tags[i]] = 1;
22 
23 …              }
24 
25 …     }
26 
27 … }
28 
29 })

現在我們來逐行分析上述的代碼意思。

1 “key” : {“day” : true}

“key”表示集合數據分組的依據。這里我們指定了“day”鍵,那么會根據集合文檔中的發布博客時間來進行分組。那有人會問,那個“true”有什么意義?如果指定了{“day” : true},那么在分組的結果中就會顯示每組“day”的鍵值。

1 “initial” : {“tags” : {}}

這個大家可能會迷惑,initial是初始化的意思,分組為什么還要初始化呢?SQL好像也沒有類似的概念啊。這個就是兩者不同的地方了。這里是為了初始化累加器的鍵值,我們可以把這個所謂的“累加器”當作一個文檔,這個文檔中存放是的在分組過程中收集、計算出的信息,不一定是集合文檔中的原信息,這里需要注意。“tags”表示每個分組中第一個文檔對應調用“$reduce”指定函數時的參數初始化,后續該分組的文檔對應調用“$reduce”指定函數時,會不斷“累加”,保留住每次對“tags”所做的更新結果。有人可能不理解上面一句在講什么,可以結合下面的解釋來理解。

1 “$reduce” : function(doc, prev) { … }

看到這里可能有些人就徹底崩潰了,我也是,不就分個組嗎,怎么把函數都整出來,還是js的。其實我們可以把這里的函數理解為分組過程,在分組的過程中,我們又人為地添加了一些操作,比如信息收集、匯總、統計等等。“doc”代表分組過程中的每一個集合中的文檔,而“prev”則代表“累加器文檔”的累加狀態,當一個分組的組員划分完畢時,這個“prev”文檔中的鍵值對的最終狀態就是我們想要的結果。這里需要注意函數體中的“prev.tags”和“doc.tags”是不同的,不信我們可以看結果。

按照上面的代碼分類后得到的結果如下所示:

1 {“day” : “2012-12-14”, “tags” : {“java” : 1, “nosql” : 2, “spring” : 1, “mongo” : 1}}
2 
3 {“day” : “2012-5-10”, “tags” : {“struts2” : 1, “hibernate” : 1, “spring” : 1}}
4 
5 {“day” : “2012-4-3”, “tags” : {“C#” : 1, “SQL” : 1}}

看出不同了嗎?上面的結果中,“tags”鍵的值是一個內嵌文檔,而這對應的就是“pre.tags”。“doc.tags”表示的原集合文檔中“tags”鍵的多值數組。

我們要找的是每天最熱門的標簽,顯然我們上面的結果還有些多余的信息,那么就讓我們使用“finalize”鍵來進行精簡吧,還是先看代碼。

 1 >db.posts.group({
 2 
 3 … “key” : {“day” : true},
 4 
 5 … “initial” : {“tags” : {}},
 6 
 7 … “$reduce” : function (doc, prev) {
 8 
 9for (i in doc.tags) {
10 
11if (doc.tags[i] in prev.tags) {
12 
13 …                       prev.tags[doc.tags[i]]++;
14 
15 …              }
16 
17else
18 
19 …              {
20 
21 …                       prev.tags[doc.tags[i]] = 1;
22 
23 …              }
24 
25 …     }
26 
27 … },
28 
29 … “finalize” : function (prev) {
30 
31var mostPopular = 0;
32 
33for (i in prev.tags) {
34 
35if (prev.tags[i] > mostPopular) {
36 
37 …                       prev.tag = i;
38 
39 …                       mostPopular = prev.tags[i];
40 
41 …              }
42 
43 …     }
44 
45delete prev.tags;
46 
47 … }
48 
49 })

“finalize”附帶的函數,在每個分組結果傳遞到客戶端之前會被調用一次,從而可以對分組結果進行“修剪”。從上面我們可以看出,原先結果中的“tags”鍵值給刪去了,加上了一個“tag”鍵值,所以最終的結果如下:

1 {“day” : “2012-12-14”, “tag” : { “nosql”}
2 
3 {“day” : “2012-5-10”, “tags” : {“struts2” : 1}}
4 
5 {“day” : “2012-4-3”, “tags” : {“C#” : 1}}

在舉個例子,本來我在剛看過上面的那段代碼后,想在自己寫的示例系統——用戶管理系統中,用PHP結合group功能實現用戶的愛好分類統計,但由於剛開始我也一直處於迷糊狀態,所以一直沒想出解決辦法,今天通過仔細閱讀PHP開發手冊的例子,終於實現了這一功能。下面是用戶集合中的文檔結構:

1 { "_id" : ObjectId("50f0c0ca323365ec0c000006"), "username" : "大胖", "age" : 23, "birthday" : ISODate("1989-10-29T16:00:00Z"), "interest" : [ "籃球", "足球", "乒乓球", "高爾夫球" ] }
2 
3 { "_id" : ObjectId("50f35b6f323365f00c000001"), "username" : "六福", "age" : 24, "birthday" : ISODate("1988-05-05T15:00:00Z"), "interest" : [ "乒乓球", "籃球" ] }
4 
5 { "_id" : ObjectId("50f37257323365ec0c000000"), "username" : "七喜", "age" : 30, "birthday" : ISODate("1984-09-30T16:00:00Z"), "interest" : [ "足球", "橄欖球" ] }
6 
7

那么如何用group方法來統計出喜愛籃球的有多少人,喜愛足球的有多少人等信息呢?現在我就用Mongo——PHP驅動提供的API來實現group的功能。有興趣的人可以根據下面的代碼簡練出Mongo語法的代碼。

 1 /*
 2 
 3      * 統計愛好分類信息
 4 
 5      */
 6 
 7     public function groupByInterest()
 8 
 9     {
10 
11        $key = array();   //$key沒有指定分組依據,那么所有文檔認為屬於同一組
12 
13        $initial = array('interests' => array());
14 
15        $reduce = 'function(obj, prev) {
16 
17            for (i in obj.interest)
18 
19            {
20 
21               if (obj.interest[i] in prev.interests)
22 
23               {
24 
25                   prev.interests[obj.interest[i]]++;
26 
27               }
28 
29               else
30 
31               {
32 
33                   prev.interests[obj.interest[i]] = 1;
34 
35               }
36 
37            }
38 
39        }';
40 
41        $g = $this->users->group($key, $initial, $reduce);
42 
43        return $g['retval'];
44 
45     }

注意上面代碼中的“$key = array();”,我沒有指定分組依據的鍵名,有人會問,這樣不會錯嗎?答案是不會的。當我們沒有指定分組依據的鍵名時,那么集合中所有文檔被認為屬於同一組。我之所以這么說是我從分組結果中分析出來的,但PHP開發手冊上的例子說的是如果在這種情況下,集合中的文檔是各自獨立成為一組的,但我堅持自己的說法,因為我看得到的結果是這樣的,如果有哪位朋友可以給個確切說明的話,歡迎指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM