在mongodb的日期分组

时间:2011-03-02 14:31:28

标签: date group-by mongodb

我正在开展一个项目,我正在跟踪某个主题的点击次数。

我正在使用mongodb,我必须按日期分组点击次数(我希望将数据分组15天)。

我在mongodb中使用以下格式存储数据

{ 
   "_id" : ObjectId("4d663451d1e7242c4b68e000"), 
  "date" : "Mon Dec 27 2010 18:51:22 GMT+0000 (UTC)", 
  "topic" : "abc", 
  "time" : "18:51:22"
}
{ 
    "_id" : ObjectId("4d6634514cb5cb2c4b69e000"), 
    "date" : "Mon Dec 27 2010 18:51:23 GMT+0000 (UTC)", 
    "topic" : "bce", 
    "time" : "18:51:23"
}

我想对主题点击次数进行分组:abc按天(15天)..我知道如何分组但是如何按日期分组存储在我的数据库中

我正在寻找以下格式的结果

[
  {
    "date" : "date in log",
    "click" : 9 
  },  
  {
    "date" : "date in log",
    "click" : 19
  },  
]

我已编写代码,但只有在日期为字符串时才会起作用(代码在这里http://pastebin.com/2wm1n1ix) ...请指导我如何分组

10 个答案:

答案 0 :(得分:66)

使用Mongo聚合框架的新答案

在提出并回答了这个问题后,10gen发布了带有聚合框架的Mongodb 2.2版,现在这是进行此类查询的更好方法。此查询有点挑战性,因为您希望按日期分组,并且存储的值是时间戳,因此您必须执行某些操作将时间戳转换为匹配的日期。出于示例的目的,我将编写一个获得正确计数的查询。

db.col.aggregate(
   { $group: { _id: { $dayOfYear: "$date"},
               click: { $sum: 1 } } }
   )

这将返回如下内容:

[
    {
        "_id" : 144,
        "click" : 165
    },
    {
        "_id" : 275,
        "click" : 12
    }
]

您需要使用$match将查询限制在您感兴趣的日期范围内,并$project_id重命名为date。如何将一年中的某一天转换回日期作为读者的练习。 : - )

10gen有一个方便的SQL to Mongo Aggregation conversion chart值得加入书签。还有一篇关于date aggregation operators的具体文章。

获得一点点发烧友,你可以使用:

db.col.aggregate([
  { $group: {
      _id: {
        $add: [
         { $dayOfYear: "$date"}, 
         { $multiply: 
           [400, {$year: "$date"}]
         }
      ]},   
      click: { $sum: 1 },
      first: {$min: "$date"}
    }
  },
  { $sort: {_id: -1} },
  { $limit: 15 },
  { $project: { date: "$first", click: 1, _id: 0} }
])

这将为您提供最新的15天,并在date字段中的每一天返回一些日期时间。例如:

[
    {
        "click" : 431,
        "date" : ISODate("2013-05-11T02:33:45.526Z")
    },
    {
        "click" : 702,
        "date" : ISODate("2013-05-08T02:11:00.503Z")
    },
            ...
    {
        "click" : 814,
        "date" : ISODate("2013-04-25T00:41:45.046Z")
    }
]

答案 1 :(得分:33)

迟到的答案,但是对于记录(对于来到此页面的任何其他人):您需要使用'keyf'参数而不是'key',因为您的密钥实际上将是事件的日期(即从日期中提取的“日期”)而不是日期本身。这应该做你想要的:

db.coll.group(
{
    keyf: function(doc) {
        var date = new Date(doc.date);
        var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear()+'';
        return {'day':dateKey};
    },
    cond: {topic:"abc"},
    initial: {count:0},
    reduce: function(obj, prev) {prev.count++;}
});

有关更多信息,请查看MongoDB关于聚合和组的文档页面:http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group

答案 2 :(得分:18)

这可以帮助

return new Promise(function(resolve, reject) {
db.doc.aggregate(
            [
                { $match: {} },
                { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date" } }, count: { $sum: 1 } } },
                { $sort: { _id: 1 } }
            ]
        ).then(doc => {
            /* if you need a date object */
            doc.forEach(function(value, index) {
                  doc[index]._id = new Date(value._id);
              }, this);
            resolve(doc);
        }).catch(reject);
}

答案 3 :(得分:4)

尚未与MongoDB合作过多,所以我不完全确定。但是你不能使用完整的Javascript吗? 因此,您可以使用Javascript Date类解析日期,创建日期,并将其设置为“out”属性。如果密钥已经存在,则始终添加一个,否则使用value = 1(第一次单击)创建新密钥。以下是具有自适应reduce功能的代码(未经测试的代码!):

db.coll.group(
{
   key:{'date':true},
   initial: {retVal: {}},
   reduce: function(doc, prev){
              var date = new Date(doc.date);
              var dateKey = date.getFullYear()+''+date.getMonth()+''+date.getDate();
              (typeof prev.retVal[dateKey] != 'undefined') ? prev.retVal[dateKey] += 1 : prev.retVal[dateKey] = 1;
            }, 
   cond: {topic:"abc"}
}
)

答案 4 :(得分:2)

另一个迟到的答案,但仍然。因此,如果您只想在一次迭代中完成并获得按日期和主题分组的点击次数,则可以使用以下代码:

db.coll.group(
{
   $keyf : function(doc) {
       return { "date" : doc.date.getDate()+"/"+doc.date.getMonth()+"/"+doc.date.getFullYear(),
                "topic": doc.topic };
    },
    initial: {count:0},
    reduce: function(obj, prev) { prev.count++; }
 })

此外,如果您想按照建议优化查询,可以使用日期的整数值(提示:使用valueOf(),对于关键日期而不是字符串,但对于我的示例,速度是相同的。< / p>

此外,定期检查MongoDB文档总是明智的,因为他们不断添加新功能。例如,使用将在2.2版本中发布的新聚合框架,您可以更轻松地实现相同的结果http://docs.mongodb.org/manual/applications/aggregation/

答案 5 :(得分:2)

感谢@mindthief,您的回答有助于解决我今天的问题。下面的功能可以更轻松地分组,希望可以帮助其他人。

/**
 * group by day
 * @param query document {key1:123,key2:456}
 */
var count_by_day = function(query){
    return db.action.group(
    {
        keyf: function(doc) {
            var date = new Date(doc.time);
            var dateKey = (date.getMonth()+1)+"/"+date.getDate()+"/"+date.getFullYear();
            return {'date': dateKey};
        },
        cond:query,
        initial: {count:0},
        reduce: function(obj, prev) {
          prev.count++;
        }
    });
}

count_by_day({this:'is',the:'query'})

答案 6 :(得分:2)

这个问题已经有很多答案,但是我对其中的任何一个都不满意。多年来,MongoDB有所改进,现在有更简单的方法可以做到这一点。 Jonas Tomanga的答案是正确的,但有点太复杂了。

如果您使用的是MongoDB 3.0或更高版本,请按照以下方法按日期进行分组。我从$match聚合开始,因为作者还询问了如何限制结果。

db.yourCollection.aggregate([
  { $match: { date: { $gte: ISODate("2019-05-01") } } },
  { $group: { _id: { $dateToString: { format: "%Y-%m-%d", date: "$date"} }, count: { $sum: 1 } } },
  { $sort: { _id: 1} }
])

答案 7 :(得分:2)

要在mongodb中按日期获取数据组

db.getCollection('supportIssuesChat').aggregate([
{
        $group : {
           _id :{ $dateToString: { format: "%Y-%m-%d", date: "$createdAt"} },
           list: { $push: "$$ROOT" },
           count: { $sum: 1 }
        }
}
])

答案 8 :(得分:1)

如果您想直接返回Date oject

然后,而不是应用Date Aggregation Operators,而是应用“日期数学”来舍入日期对象。这通常是可取的,因为所有驱动程序都代表BSON日期,其形式通常用于所有语言的日期操作:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

或者,如果问题中暗示所需的分组间隔是15天的“桶”,那么只需将其应用于$mod中的数值:

db.datetest.aggregate([
    { "$group": {
        "_id": {
            "$add": [
                { "$subtract": [
                    { "$subtract": [ "$date", new Date(0) ] },
                    { "$mod": [
                        { "$subtract": [ "$date", new Date(0) ] },
                        1000 * 60 * 60 * 24 * 15
                    ]}
                ]},
                new Date(0)
            ]
        },
        "click": { "$sum": 1 }
    }}
])

应用的基本数学是当你$subtract两个Date个对象时,返回的结果将是数字上不同的毫秒数。所以epoch由Date(0)表示为你所拥有的任何语言构造函数的转换基础。

使用数值,“模数”($mod)用于将日期四舍五入(从除法中减去余数)到所需的间隔。成为:

  

1000毫秒x 60秒* 60分钟* 24小时= 1天

  

1000毫秒x 60秒* 60分钟* 24小时* 15天= 15天

因此,无论您需要什么时间间隔,它都是灵活的。

通过上面的相同标记,“numeric”值和Date对象之间的$add操作将返回一个Date对象,该对象相当于两个对象组合的millseconds值(epoch)是0,因此0加上差值是转换日期。)

在以下列表中轻松表示和重现:

var now = new Date();
var bulk = db.datetest.initializeOrderedBulkOp();

for ( var x = 0; x < 60; x++ ) {
    bulk.insert({ "date": new Date( now.valueOf() + ( 1000 * 60 * 60 * 24 * x ))});
}

bulk.execute();

以15天的间隔运行第二个例子:

{ "_id" : ISODate("2016-04-14T00:00:00Z"), "click" : 12 }
{ "_id" : ISODate("2016-03-30T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-03-15T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-29T00:00:00Z"), "click" : 15 }
{ "_id" : ISODate("2016-02-14T00:00:00Z"), "click" : 3 }

或类似的分配取决于列表运行时的当前日期,当然15天的间隔将与纪元日期一致。

使用“Math”方法更容易调整,特别是如果您想调整聚合输出中不同时区的时间段,您可以通过添加/减去UTC的数字差异来进行类似的数字调整。

答案 9 :(得分:0)

当然,that是一个很好的解决方案。除此之外,您可以将日期按日期分组为字符串(如that answer建议的那样),或者您可以通过预测日期字段(汇总)来获取日期的开头:

{'$project': {
    'start_of_day': {'$subtract': [
        '$date',
        {'$add': [
            {'$multiply': [{'$hour': '$date'}, 3600000]},
            {'$multiply': [{'$minute': '$date'}, 60000]},
            {'$multiply': [{'$second': '$date'}, 1000]},
            {'$millisecond': '$date'}
        ]}
    ]},
}}

它给你这个:

{
    "start_of_day" : ISODate("2015-12-03T00:00:00.000Z")
},
{
    "start_of_day" : ISODate("2015-12-04T00:00:00.000Z")
}

它有一些优点:您可以使用日期类型(不是数字或字符串)操作日期,它允许您在以下聚合操作中使用所有date aggregation operators并在输出中提供日期类型。