MongoDB高級查詢[聚合Group]

接上一篇... 見: http://my.oschina.net/zhzhenqin/blog/97949 javascript

  • Group

爲了方便我仍是把個人表結構貼上來: java

和數據庫同樣group經常用於統計。MongoDB的group還有不少限制,如:返回結果集不能超過16M, group操做不會處理超過10000個惟一鍵,好像還不能利用索引[不很肯定]。 shell


Group大約須要一下幾個參數。 數據庫

  1. key:用來分組文檔的字段。和keyf二者必須有一個
  2. keyf:能夠接受一個javascript函數。用來動態的肯定分組文檔的字段。和key二者必須有一個
  3. initial:reduce中使用變量的初始化
  4. reduce:執行的reduce函數。函數須要返回值。
  5. cond:執行過濾的條件。
  6. finallize:在reduce執行完成,結果集返回以前對結果集最終執行的函數。可選的

下面我用Java對他們作一些測試。 數組

咱們以age年齡統計集合中存在的用戶。Spring Schema和上次的同樣。有了MongoTemplate對象咱們能夠作全部事的。以age統計用戶測試代碼如: 函數


@Test
    public void testGroupBy() throws Exception {
        String reduce = "function(doc, aggr){" +
                "            aggr.count += 1;" +
                "        }";
        Query query = Query.query(Criteria.where("age").exists(true));
        DBObject result = mongoTemplate.getCollection("person").group(new BasicDBObject("age", 1), 
                query.getQueryObject(), 
                new BasicDBObject("count", 0),
                reduce);
        
        Map map = result.toMap();
        System.out.println(map);
        for (Map.Entry o : map.entrySet()) {
            System.out.println(o.getKey() + "  " + o.getValue());
        }
    }

keynew BasicDBObject("age", 1) 測試

cond爲:Criteria.where("age").exists(true)。即用戶中存在age字段的。 ui

initial爲:new BasicDBObject("count", 0),即初始化reduce中人的個數爲count0。假如咱們想在查詢的時候給每一個年齡的人增長10個假用戶。咱們只須要傳入BasicDBObject("count", 10). spa

reduce爲:reducejavascript函數 .net

上面的執行輸出如:

2  [age:23.0, count:1.0]
1  [age:25.0, count:1.0]
0  [age:24.0, count:1.0]

前面的是一個序號,是Mongo的java-driver加上去的。咱們能夠看到結果在後面。

不過你可能都以爲reduce這段代碼用Java寫的太繁瑣了,要是和Python同樣支持多行字符串多好啊。 我也煩。下面的例子我用Groovy寫,不過我儘可能寫的貼近Java

一樣的reduce,用Groovy只需這樣:

def reduce = """
        function(doc, aggr){
             aggr.count += 1;
        }
               """;

age統計用戶這是基本的需求了。下面我來幾個高級點的。

個人表結構中用戶的朋友[myFriends]是一個數組類型的,mongo提供的查詢中對數組查詢時數組長度$size只能用來判斷,卻不能用來輸出[至少我沒找到]。那麼咱們用group操做來統計一下每一個人有幾個朋友。測試代碼如:

@Test
    void testFriendGroupUserFriendCount() throws Exception {
        def reduce = """
                     function(doc, aggr){
                     aggr.manId = doc.manId;
                         doc.myFriends.forEach(function(z){
                              aggr.count += 1;
                         })
                      }
                      """;
 
        Query query = Query.query(Criteria.where("myFriends").exists(true));
        DBObject result = mongoTemplate.getCollection("person").group(
                new BasicDBObject("manId", 1),
                query.getQueryObject(),
                new BasicDBObject("count", 0),
                reduce);
 
        Map map = result.toMap();
        for (Map.Entry o : map.entrySet()) {
            System.out.println(o.getKey() + " ==> " + o.getValue());
        }
    }
執行結果如:



2 ==> [manId:345678, count:2.0]
1 ==> [manId:234567, count:2.0]
0 ==> [manId:123456, count:4.0]

上面的reduce中遍歷文檔的數組用了forEach,我記得好像Javascript中不能這麼作吧? 我對js不熟,但願牛人解答下。

 上面的例子一直都沒用到finallize,下面的測試我但願能用上。

咱們統計每一個人最喜歡水果是哪一個?每一個人都有n個本身的喜歡的水果,fruits.boost是每一個水果的權重。那麼咱們找出最喜歡的那個?

測試代碼:

@Test
    void testGroupByFruitFinallize() throws Exception {
        def reduce = """
                function(doc, out) {
                    out.name = doc.manName;
                    for(i in doc.fruits) {
                       if(doc.fruits[i] in out.fruits) {
                           out.fruits[doc.fruits[i].fruitId]++;
                      } else {
                           out.fruits[doc.fruits[i].fruitId] = 1;
                       }
                    }
                }
                """;
 
        def finallizer = """
                function(out) {
                    var mostPopular = 0;
                    for(i in out.fruits) {
                        if(out.fruits[i] > mostPopular) {
                            out.fruitId = i;
                           mostPopular = out.fruits[i];
                        }
                    }
                   delete out.fruits;
                    return out;
                }
                """;
 
        Query query = new BasicQuery("{}");
        long time = System.currentTimeMillis();
        DBObject result = mongoTemplate.getCollection("person").group(new BasicDBObject("fruits", true),
                query.getQueryObject(),
                new BasicDBObject("fruits",  new BasicDBObject()),
                reduce,
                finallizer);
        System.out.println("use time: " + (System.currentTimeMillis() - time));
 
        Map map = result.toMap();
        for (Map.Entry o : map.entrySet()) {
            System.out.println(o.getKey() + "  " + o.getValue());
        }
    }
它的測試輸出爲:

2  [name:ZhenZi, fruitId:www]
1  [name:YangYan, fruitId:www]
0  [name:ZhenQin, fruitId:aaa]

OK,他完成了。

關於keyf我找了不少資料,目前還沒發現怎麼使用。但願有人能解答下。

相關文章
相關標籤/搜索