B站的文本编辑器真的太难用了。

为了更好的阅读体验建议去我的github blog看这篇文章:https://nintha.github.io/2018/05/15/MongoDB%E5%9F%BA%E4%BA%8E%E6%97%B6%E9%97%B4%E6%AE%B5%E7%9A%84%E8%81%9A%E5%90%88%E6%9F%A5%E8%AF%A2/

前言

最近写了一个爬虫对B站的视频统计数据进行追踪,每2分钟爬取一次存在mongo里,然后用这些数据画折线图。这个时候问题来了,如果我爬取了一年的数据,进行数据展示的时候,不应该把一年的数据都从数据库里读取出来,对于年这样大粒度统计,应该以每天抽取一条记录就OK了;当想看一天内的数据变化,又要以分钟为粒度进行记录抽取。

基本的mongo查询语法已经难以解决均匀抽样查询记录了,这时就需要聚合查询这样的工具。

Mongo聚合查询

先看下存储的数据结构,其中ctime代表了这条记录的创建时间。

{

"_id" : ObjectId("5ae738594cb3ed1a60210042"),

"aid" : NumberLong(22755224),

"view" : NumberLong(18649),

"danmaku" : NumberLong(533),

"favorite" : NumberLong(1027),

"reply" : NumberLong(553),

"coin" : NumberLong(1896),

"share" : NumberLong(172),

"ctime" : ISODate("2018-04-30T15:38:01.120Z")

}

查询的目标是这样的,对于指定的时间区间,返回最多300条(这个数量大概是性能和图表显示效果的平衡吧)。

思路是这样的:

先对把时间区间平均分成300份,对每一个份的小时间区间中按一个统一的规律取一条记录作为小时间区间的代表记录(这个统一的规律可以用最大值、最小值、平均值等,我这里使用了最大值,为了保证可以看到最新的一条追踪记录)。

时间区间均分这个需求可以用mongo聚合中的$group操作进行处理,不过值得注意的是$group是对某一列中拥有相同值的记录进行分组。那如何把一段时间内的记录归并到同一组呢?一种可行的操作是把时间转换成时间戳,就是长整形数字,把时间区间同样转换为一个数字,两者相除再抹除小数部分就可以了。

把这个思路用Mongo原生API实现:db.trace_video_stat.aggregate([

{"$match":{

"aid":22755224,

"ctime": {

'$gte':ISODate("2018-05-10T00:00:00Z"),

'$lt':ISODate("2018-05-10T00:30:00Z")

}

}},

{"$group": {

"_id": {

"$subtract": [

{ "$subtract": [ "$ctime", new Date("1970-01-01") ] },

{ "$mod": [

{ "$subtract": [ "$ctime", new Date("1970-01-01") ] },

1000 * 60 * 30  /*聚合时间段,30分钟*/

]}

]

},

"coin": {'$max': '$coin'},

"view": {'$max': '$view'},

"danmaku": {'$max': '$danmaku'},

"favorite": {'$max': '$favorite'},

"reply": {'$max': '$reply'},

"coin": {'$max': '$coin'},

"share": {'$max': '$share'},

"ctime": {'$max': '$ctime'},

}},

{"$sort": {

'ctime': 1

}}

])

这里先用$match去除对应时间区间的数据,再用$group进行分组处理,最后用$sort进行排序。

查询结果:

[{

"_id" : NumberLong(1525910400000),

"coin" : NumberLong(8421),

"view" : NumberLong(109018),

"danmaku" : NumberLong(2859),

"favorite" : NumberLong(4939),

"reply" : NumberLong(1799),

"share" : NumberLong(837),

"ctime" : ISODate("2018-05-10T00:00:22.563Z")

},

// omitted

]

这里出现的字段是和$group中处理的字段一一对应,一些没有用到的字段就会被省略。注意这里的_id并不是mongo的ObjectId。

SpringData mongoTemplate

实际开发中我们可能会用到SpringData封装mongoTemplate来操作数据库。同样的,使用mongoTemplate也可以很方便的实现一样的效果。

public List getVideoStat(long aid, Date startTime, Date endTime) {

long interval = (endTime.getTime() - startTime.getTime()) / 300;

TypedAggregation agg = Aggregation.newAggregation(

VideoStat.class,

match(Criteria.where("aid").is(aid).and("ctime").gte(startTime).lte(endTime)),

project("ctime", "coin", "share", "view", "danmaku", "favorite", "reply")

.andExpression("ceil((ctime - [0]) / [1])", new Date(0), interval)

.as("cdate"),

group("cdate")

.max("view").as("view")

.max("coin").as("coin")

.max("danmaku").as("danmakuu")

.max("favorite").as("favorite")

.max("reply").as("reply")

.max("share").as("share")

.max("ctime").as("ctime"),

sort(Sort.Direction.ASC, "ctime"));

return mongoTemplate.aggregate(agg, "TRACE_VIDEO_STAT", VideoStat.class).getMappedResults();

}

match方法缩小数据范围,project方法决定需要聚合的字段,group方法决定被分组的列表,后面的链时调用时对组内数据的统计处理,最后的sort是排序。

这里用到的SpEL表达式 .andExpression("ceil((ctime - [0]) / [1])", new Date(0), interval),[0]是占位符,表达式支持对时间类型的直接运算,默认会转换为时间戳进行处理,还是非常方便的。

B站的文本编辑器真的太难用了。

为了更好的阅读体验建议去我的github blog看这篇文章:https://nintha.github.io/2018/05/15/MongoDB%E5%9F%BA%E4%BA%8E%E6%97%B6%E9%97%B4%E6%AE%B5%E7%9A%84%E8%81%9A%E5%90%88%E6%9F%A5%E8%AF%A2/

mongodb 日期分组聚合_MongoDB基于时间段的聚合查询相关推荐

  1. mongodb 日期分组聚合_mongoose聚合aggregate按日期分组计算

    简介 在MongoDB中,聚合(aggregate)主要用于进行处理数据,比如统计求和,求平均数等,并返回计算后的数据结果,这给我们带来了很多便捷之处,因为可以在读取数据的同时进行数据处理. 场景介绍 ...

  2. mongodb 分组聚合_MongoDB聚合嵌套分组

    我有资产收集,其中包含数据 { "_id" : ObjectId("5bfb962ee2a301554915"), "users" : [ ...

  3. mongodb aggregate按日期分组统计及spring mongo实现

    如需转载请注明出处: mongodb aggregate按日期分组统计及spring mongo实现 实现的需求 传入毫秒级开始时间戳和结束的时间戳,根据当前状态currentStatus.statu ...

  4. limit mongodb 聚合_MongoDB 聚合操作(转)

    在MongoDB中,有两种方式计算聚合:Pipeline 和 MapReduce.Pipeline查询速度快于MapReduce,但是MapReduce的强大之处在于能够在多台Server上并行执行复 ...

  5. 力扣刷题学习SQL篇——1-8 查询(按日期分组销售产品——利用聚合函数GROUP_CONCAT)

    力扣刷题学习SQL篇--1-8 查询(按日期分组销售产品--利用聚合函数GROUP_CONCAT) 1.题目 2.解法 3.group_concat() 1.题目 题目链接:https://leetc ...

  6. R语言使用ggplot2包使用geom_density()函数绘制分组密度图(分组拆分画板基于facet)实战(density plot)

    R语言使用ggplot2包使用geom_density()函数绘制分组密度图(分组拆分画板基于facet)实战(density plot) 目录 R语言使用ggplot2包使用geom_density

  7. MongoDB日期转换

    1.查询日期分组,把Long时间转换 db.getCollection('Warehouse.InoutstockDetail').aggregate([ { $match:{"_id&qu ...

  8. php识别名片,基于php的聚合数据名片识别api调用实例

    基于php的聚合数据名片识别api调用实例 本代码是基于聚合数据名片识别api实现名片识别功能,使用前你需要通过http://www.doczj.com/doc/f71face3bed5b9f3f90 ...

  9. Excel 数据透视表教程大全之 04 按日期分组(教程含样本数据)

    什么是数据透视表? 您可以将数据透视表视为报告.但是,与静态报表不同,数据透视表提供数据的交互式视图.只需很少的努力(并且没有公式),您就可以从许多不同的角度查看相同的数据.您可以将数据分组,将数据分 ...

  10. mysql校验日期是否有效_Mysql 根据出生日期计算年龄 判断起始日期是否在某个有效时间段内等日期Hack汇总...

    关于Mysql格式化等常见日期处理相关函数说明,大家可以参考之前的一篇博文: mysql 根据出生日期计算年龄.日期格式化等常见日期处理相关函数使用汇总.根据以往的项目经验,本文总结并汇总了一些Mys ...

最新文章

  1. Android获取当前位置的三种方式及其使用方法
  2. linux档案与文件的的压缩与打包
  3. 现代版马拉火车!蔚来出动燃油板车运送充电车服务
  4. 学Java看什么书比较好
  5. JavaScript之浏览器大战
  6. java学习资料免费下载(持续更新ing)
  7. android低版本so加载失败,Android 4.X 系统加载 so 失败的原因分析
  8. 2020计算机校友会大学排名,2020年校友会大学排名:一个世界一流大学,一个中国一流大学...
  9. 计算机网络-什么是网络协议?
  10. buuctf|ciscn_2019_en_2 1
  11. 方向α与《孙子兵法》的基本原则
  12. 初步的解了Pathon
  13. 在国内,如何优雅的使用ChatGPT??
  14. 浅谈cpu、缓存、内存之间的关系
  15. Cypress-should()常见断言
  16. 1_ARM Cortex-M处理器简介
  17. jquery常用代码--(一)
  18. Metasploitable渗透测试实战:ms10-046
  19. fedora php mysql_Fedora 8下用Yum安装Apache+PHP+MySQL环境
  20. #coding=utf-8 from ctypes import * import time import os,pygame,threading windll.user32.BlockInpu

热门文章

  1. Pytorch深度学习(二):反馈神经网络(BPNN)
  2. stvd c语言编译器,STM8--STVD编译工具安装与程序下载
  3. 二进制原码一位乘法运算
  4. 宝峰对讲机16频率表_宝峰对讲机频率设置 设置对讲机频率的技巧
  5. 2018中国双态运维用户大会于1月13日在京举行
  6. 在线画图工具绘制流程图怎样做
  7. SIP协议栈eXosip2分析
  8. 北斗卫星导航系统简介
  9. matlab中whos怎么用,【安富莱DSP教程】第3章 Matlab 简易使用(一)
  10. 汽车电工及电子技术基础【1】