在mongodb中按date分组
我正在研究一个项目,我正在跟踪一个主题的点击次数。
我正在使用mongodb,我必须按date点击组数(我想将数据分组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" : "date in log", "click" : 9 }, { "date" : "date in log", "click" : 19 }, ]
我写了代码,但它只会工作,如果date是在string(代码在这里http://pastebin.com/2wm1n1ix )…请指导我如何分组
使用Mongo聚合框架的新答案
在这个问题被问到和回答之后,10gen发布了一个聚合框架的Mongodb 2.2版,现在是做这种查询的更好的方法。 这个查询是有点具有挑战性的,因为你想按date进行分组,所存储的值是时间戳,所以你必须做一些事情来将时间戳转换为匹配的date。 为了举例的目的,我将只写一个查询,得到正确的计数。
db.col.aggregate( { $group: { _id: { $dayOfYear: "$date"}, click: { $sum: 1 } } } )
这将返回类似于:
[ { "_id" : 144, "click" : 165 }, { "_id" : 275, "click" : 12 } ]
您需要使用$match
将查询限制在您感兴趣的date范围内,并使用$project
重新命名_id
。 如何将年份的date转换回date只是读者的一个练习。 🙂
10gen有一个方便的SQL Mongo汇总转换图表值得加书签。 还有一个关于date聚合操作符的特定文章。
花一点点时间,你可以使用:
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
字段中每天返回一些datedate
。 例如:
[ { "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") } ]
迟到的答案,但为了logging(对于这个页面的其他人):你需要使用'keyf'参数而不是'key',因为你的键实际上是date函数事件(即从date提取的“日”)而不是date本身。 这应该做你正在寻找的东西:
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
MongoDB的工作还没有那么多,所以我不完全确定。 但是你不能使用完整的Javascript?
所以你可以使用Javascript Date
类来parsing你的date,创build你的date并将其设置为“out”属性。 如果密钥已经存在,请始终添加一个,否则请将其值设为1(第一次单击)。 下面是你的代码与适应减lessfunction(未经testing的代码!):
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"} } )
这可以帮助
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); }
另一个迟到的答案,但仍然。 因此,如果您只想在一次迭代中完成此操作,并获取按date和主题分组的点击次数,则可以使用以下代码:
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++; } })
另外如果你想按照build议优化查询,你可以使用date的整数值(提示:使用valueOf()作为关键date而不是string,尽pipe对于我的例子来说速度是一样的。
此外,定期检查MongoDB文档总是明智的,因为他们不断地添加新的function。 例如,新版本的Aggregation框架将在2.2版本中发布,您可以更轻松地获得相同的结果http://docs.mongodb.org/manual/applications/aggregation/
感谢@mindthief,你的回答有助于今天解决我的问题。 下面的function可以一天比一天更容易,希望可以帮助其他人。
/** * 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'})
如果你想要一个date对象直接返回
然后,而不是应用date聚合运算符 ,而是应用“datemath”四舍五入date对象。 这通常是可取的,因为所有驱动程序代表BSONdate的forms,通常用于可能的所有语言的date操作:
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 } }} ])
所应用的基本math运算是,当您$subtract
两个Date
对象时,返回的结果将是毫秒数的差异。 所以时代是由Date(0)
表示的,作为在任何语言构造器中进行转换的基础。
使用数字值,应用“模数”( $mod
)将date四舍五入(将除法的余数减去)到所需的时间间隔。 作为:
1000毫秒×60秒* 60分钟* 24小时= 1天
要么
1000毫秒×60秒×60分钟×24小时×15天= 15天
因此,无论您需要什
同样的道理,在“数值”值和Date
对象之间的$add
操作将会返回一个Date
对象,这个Date
对象等于两个对象的毫秒值(时间点为0,因此0加上差值就是转换后的date)。
在下列清单中易于performance和重现:
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 }
或者根据运行列表的当前date进行类似的分配,当然这个15天的时间间隔自开始date起将保持一致。
使用“math”方法更容易调整,特别是如果您想调整聚合输出中不同时区的时间段,您可以通过在UTC中添加/减去数值差异进行数值调整。
当然, 这是一个很好的解决scheme。 除此之外,您可以将date按string分组(如答案build议),也可以通过投影date字段(在聚合中)来获取date的开始:
{'$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") }
它有一些优点:你可以在datetypes(不是数字或string)中操纵你的日子,它允许你在后面的聚合操作中使用所有的date聚合操作符 ,并在输出中给你datetypes。