天天看點

疑難雜症(8)-- mongodb問題:distinct too big的解決方案

在使用MonoDB 做報表彙總經常的有去重統計總數的需求,在此總結一下實作方式:

1, 直接使用distinct 語句查詢, 這種查詢會将所有查詢出來的資料傳回給使用者, 然後對查詢出來的結果集求總數(耗記憶體,耗時一些)

var len = db.student.distinct("name",{"age" : 18}).length; 
print(len)
           

注,使用這種方法查詢時,查詢的結果集大于16M 時會查詢失敗,失敗資訊如下: 

{“message” : “distinct failed: MongoError: distinct too big, 16mb cap”,”stack” : “script:1:20”}
           

2, 使用聚合函數,多次分組統計結果,最終将聚合的結果數傳回給使用者

db.student.aggregate([ {$match:{"age" : 18}}, {$project:{"name":true}}, {$group:{_id:"$name"}}, {$group:{_id:null,count:{$sum:1}}} ])
           

注,這種查詢資料量大時就不會出現如上查詢失敗的情況,而且這種查詢不管是記憶體消耗還是時間消耗都優于上面一種查詢.

spring-data-mongodb之Aggregation

這篇文章主要介紹下在data架構中如何使用Aggregation進行分組統計。

基本的操作包括:

  1. $project - 可以從子文檔中提取字段,可以重命名字段。
  2. $match - 可以實作查找的功能。
  3. $limit - 接受一個數字n,傳回結果集中的前n個文檔。
  4. $skip - 接受一個數字n,丢棄結果集中的前n個文檔。
  5. $group - 統計操作, 還提供了一系列子指令。
  6. $avg, $sum 等等函數…。
  7. $sort - 排序。

基于我們之前文章的資料我們就簡單的統計下每個使用者發過多少篇文章。

如果是用的mysql那麼查詢語句如下:

select article.author, count(*) as count from article_info as article group by article.author having count > 0
           

既然我們要用mongodb來實作,還要用架構來做,那麼首先我們得知道用原始的語句怎麼寫,如下:

db.article_info.aggregate([ { "$group": { "_id": "$author", "count": { "$sum": 1 }, "name": { "$first": "$author" } } }, { "$project": { "name": 1, "count": 1, "_id": 0 } }, { "$match": { "count": { "$gt": 0 } } } ]);
           

$group:根據author分組,然後統計次數,用$sum函數,顯示第一個名稱

$project:定義要顯示的key,1為顯示,0為不顯示

$match:過濾掉沒發過文章的使用者,次數大于0

下面看spring-data-mongodb中我們要怎麼去實作這個功能

/** 
  * 聚合使用 
  * 統計每個使用者的文章數量 
  */ 
private static void aggregation() { 
    Aggregation aggregation = Aggregation.newAggregation(
        Aggregation.match(Criteria.where("createTime").gte(startDate).lte(endDate)),
        Aggregation.project("eid"),
        Aggregation.group("eid"),
        Aggregation.group("eid").count().as("count")
    );

    AggregationResults<BasicDBObject> outputType = mongoTemplate.aggregate(aggregation, "集合名", BasicDBObject.class);

    List<BasicDBObject> tagCount = outputType.getMappedResults();
    for (BasicDBObject basicDBObject : tagCount) {
        if (basicDBObject.containsField("count")) {
            System.out.println(basicDBObject.getInt("count"));
        }
    } 
}
           

按照原始的語句,用架構的文法拼出來就可以了

當然還有的小夥伴還是用習慣了java驅動的寫法,也可以,就是沒上面簡潔

private static void aggreate() { 
    List<DBObject> pipeline = new ArrayList<DBObject>(); 
    BasicDBObject group = new BasicDBObject(); 
    group.put("$group", new BasicDBObject("_id","$author").append("count", new BasicDBObject("$sum",1)).append("name", new BasicDBObject("$first","$author")));
    BasicDBObject project = new BasicDBObject(); project.put("$project", new BasicDBObject("name",1).append("count", 1).append("_id", 0)); 

    pipeline.add(group); 
    pipeline.add(project); 
    AggregationOutput output = mongoTemplate.getCollection("article_info").aggregate(pipeline); 
    Iterable<DBObject> iterable = output.results(); 
    for (DBObject dbObject : iterable) {
        System.out.println(dbObject); 
    } 
}