如何在MongoDB中进行双重分组?

时间:2014-08-18 11:48:14

标签: mongodb

第一次熟悉MongoDB时,问题出现在分组数据时。
给出两天的数据:

db.test.insert({
    "_id" : ObjectId("13edebb315d8952400407343"),
    "create_at" : ISODate("2012-12-19T12:00:00.000Z"),
    "item" : {
        "tags" : [ 
            "aaaa"
        ],
        "event" : "accepted",
    }
});

db.test.insert({
    "_id" : ObjectId("13edebb39e60c73800b35727"),
    "create_at" : ISODate("2012-12-19T12:05:00.000Z"),
    "item" : {
        "tags" : [ 
            "aaaa"
        ],
        "event" : "delivered"
    }
});

db.test.insert({
    "_id" : ObjectId("13edebb315d8952400407344"),
    "create_at" : ISODate("2012-12-19T13:40:00.000Z"),
    "item" : {
        "tags" : [ 
            "bbbb"
        ],
        "event" : "accepted",
    }
});

db.test.insert({
    "_id" : ObjectId("13edebb39e60c73800b35728"),
    "create_at" : ISODate("2012-12-19T13:45:00.000Z"),
    "item" : {
        "tags" : [ 
            "bbbb"
        ],
        "event" : "delivered"
    }
});

db.test.insert({
    "_id" : ObjectId("13edebb315d8952400407345"),
    "create_at" : ISODate("2012-12-20T16:30:00.000Z"),
    "item" : {
        "tags" : [],
        "event" : "accepted",
    }
});

db.test.insert({
    "_id" : ObjectId("13edebb39e60c73800b35729"),
    "create_at" : ISODate("2012-12-20T16:35:00.000Z"),
    "item" : {
        "tags" : [],
        "event" : "delivered"
    }
});


输出需要得到结果:

{
  "total_count": 6
  "items": [
    {
      "total_count": 2,
      "created_at": "Wed, 19 Dec 2012 00:00:00 GMT",
      "tags": {
        "aaaa": 1,
        "bbbb": 1
      },
      "event": "sent"
    },
    {
      "total_count": 2,
      "created_at": "Wed, 19 Dec 2012 00:00:00 GMT",
      "tags": {
        "aaaa": 1,
        "bbbb": 1
      },
      "event": "delivered"
    },
    {
      "total_count": 1,
      "created_at": "Wed, 20 Dec 2012 00:00:00 GMT",
      "tags": {},
      "event": "sent"
    },
    {
      "total_count": 1,
      "created_at": "Wed, 20 Dec 2012 00:00:00 GMT",
      "tags": {},
      "event": "delivered"
    }
}


仍设法构成必要数据的一部分,请求:

db.test.aggregate([ 
  {$group:      
   {          
     _id:{event:'$item.event', doy:{$dayOfYear:'$create_at'} },
     total_count:{$sum:1},
     created_at:{$first: '$create_at'},
     tags: {$addToSet: '$item.tags'}
   },
  },
  {$project:{total_count:1,  _id:0, event:'$_id.event', created_at:1, tags:1}}
])

但是如何获得有关标签的必要信息以及标签数组及其编号? 以及指示00:00:00开始的日期?

2 个答案:

答案 0 :(得分:0)

这是一个简单的聚合查询,可帮助您入门。它可以获得你想要的大部分内容,但形式有点不同。

> db.test.aggregate([
    { "$unwind" : "$item.tags" }, 
    { "$group" : 
        { "_id" : { 
            "event" : "$item.event", 
            "day" : { "$dayOfYear" : "$create_at" }, 
            "tag" : "$item.tags" 
            }, 
        "total_count" : { "$sum" : 1 } 
        } 
    }
])
{ "_id" : { "event" : "delivered", "day" : 354, "tag" : "bbbb" }, "total_count" : 1 }
{ "_id" : { "event" : "accepted", "day" : 354, "tag" : "bbbb" }, "total_count" : 1 }
{ "_id" : { "event" : "delivered", "day" : 354, "tag" : "aaaa" }, "total_count" : 1 }
{ "_id" : { "event" : "accepted", "day" : 354, "tag" : "aaaa" }, "total_count" : 1 }

由于您希望按(事件,标记,日期)计算文档数量,因此此聚合查询计算一个文档,其中包含每个唯一三元组所需的计数(事件,标记,日期),这是最简单的方法做到这一点。总计数只是聚合结果的数量。

为了把这一天放回日期,我认为你必须做客户端的事情,因为我不知道任何产生日期的聚合管道运营商。尽管如此,它并不困难,因为您可以通过更改$group阶段并使用$project来输出所有相关日期信息。如果您愿意,可以在管道中重建字符串日期。

您会注意到没有标签的文件不会被计算在内。这是$unwind的必然结果。最简单的方法是使用一个代表" no tag"的虚拟标签。纯粹通过聚合框架解决这个问题会很麻烦。

最后,我想指出的一件事是,如果你可能会忘记:如果你在$dayOfYear上分组,如果你的数据跨越多年,你可以将不同年份的文档组合在一起。确保这是您的意图,或将更多日期信息添加到组密钥_id

答案 1 :(得分:0)

wdberkeley在这个问题上做了很好的分析。我在下面添加我的:

  1. 标签中的元素(例如" aaaa")将作为最终子文档中的关键字。我不知道聚合管道可以做到这一点。
  2. 输出需要tags:{tag:count, ...}的形式,因此如果处于管道操作样式,将使用$ unwind运算符。输出需要具有空标记的文档仍需要保存,但$ unwind运算符将忽略这些文档。
  3. 管道操作中禁止使用JS代码,但管道操作无法实现该日期格式(例如"created_at" : "Wed, 20 Dec 2012 00:00:00 GMT")。
  4. 结论:使用mapReduce而不是聚合管道。 以下代码在mongo shell中传递。

    function map() {
        var date = this.create_at;
        var dateStr = date.getFullYear() + "-" + (date.getMonth() + 1) + "-"
                + date.getDate();
        var tags = {};
        var tagsTemp = this.item.tags;
        if (tagsTemp != null) {
            for (var x = 0; x < tagsTemp.length; x++) {
                var tag = tagsTemp[x];
                var count = tags[tag];
                count = (count == null) ? 1 : (count + 1);
                tags[tag] = count;
            }
        }
        emit({
            event : this.item.event,
            dateStr : dateStr
        }, {
            total_count: 1,
            tags : tags
        });
    }
    
    function reduce(key, values) {
        var tags = {};
        var total_count = 0;
        values.forEach(function(value) {
            for ( var tag in value.tags) {
                var count = tags[tag];
                if (count == null)
                    count = 0;
                tags[tag] = count + value.tags[tag];
            }
            total_count += value.total_count;
        });
        return {
            total_count: total_count,
            tags : tags
        };
    }
    
    
    function finalHandle(key, reduceValue) {
        reduceValue.create_at = new Date(key.dateStr).toUTCString();
        reduceValue.event = key.event;
        return reduceValue;
    }
    
    var mr = db.test.mapReduce(map, reduce, {finalize: finalHandle, out:{inline:1}});
    
    var total = 0;
    var items = [];
    
    mr.results.forEach(function(x) {
        items.push(x.value);
        total += x.value.total_count;
    });
    
    printjson({total_count: total, items: items});