MySQL的跨年周统计问题

最近在做项目的时候,发现了一个报表在进行周统计并且跨年的时候会出现问题,具体问题截图如下:(主要与%X-%V的时间格式符有关)

2016-12-18 ~~ 2017-01-13 周统计


图1

2015-12-20 ~~ 2016-01-17 周统计


图2

这两张图中,周统计中,代码中的sql时间格式化为

DATE_FORMAT(FROM_UNIXTIME(regTime,'%Y-%m-%d'),'%Y-%U')

为了让接下来的解释更加清楚,这里先贴一张MySQL的时间格式化符和描述

格式 描述
%a 缩写星期名
%b 缩写月名
%c 月,数值
%D 带有英文前缀的月中的天
%d 月的天,数值(00-31)
%e 月的天,数值(0-31)
%f 微妙
%H 小时 (00-23)
%h 小时 (01-12)
%I 小时 (01-12)
%i 分钟,数值(00-59)
%j 年的天 (001-366)
%k 小时 (0-23)
%l 小时 (1-12)
%M 月名
%m 月,数值(00-12)
%p AM 或 PM
%r 时间,12-小时(hh:mm:ss AM 或 PM)
%S 秒(00-59)
%s 秒(00-59)
%T 时间, 24-小时 (hh:mm:ss)
%U 周 (00-53) 星期日是一周的第一天
%u 周 (00-53) 星期一是一周的第一天
%V 周 (01-53) 星期日是一周的第一天,与 %X 使用
%v 周 (01-53) 星期一是一周的第一天,与 %x 使用
%W 星期名
%w 周的天 (0=星期日, 6=星期六)
%X 年,其中的星期日是周的第一天,4 位,与 %V 使用
%x 年,其中的星期一是周的第一天,4 位,与 %v 使用
%Y 年,4 位
%y 年,2 位

由表可以看出,%U是代表一周,并且是以周日当做第一天,由图2可以看出,15-12-27作为15年的最后一周,其7天的时间分别为12-27、12-28、12-29、12-30、12-31、16-01-01、16-01-02,也就是说,15年的最后一周跨到了16年,那么%U是怎么处理的呢?%U代表的是第0周开始的,使用星期天作为一周的第一天,当遇到本年的第一个星期天时,就是第 1 周了,那么在1.1到今年第一周之前的天数就当做今年的第0周

举例说明:

(1)2017的第一个周日 正好是2017-01-01,那么从这一天开始就是第 1 周,而不是第0周

(2) 2016的第一个周日,是 2016-01-03,那么2016-01-01至2016-01-03是算作第 0 周,而
把2013-01-03 至 2013-01-10 算作2013年的第 1 周

所以这里就有匪夷所思的第0周出现了,那么程序是如何格式化这个第0周的呢,这样的跨年现象,是把15年的最后一周变成只有12-27、12-28、12-29、12-30、12-31这5天,而16-01-01、16-01-02算做新的一周,也就是图2中从1.1开始算一周,到了16.1.3号又重新计算一周。换句话说,15年的最后一周的数据是图2报表中
15.12.27的数据加上16.01.01的数据。

因为说到了%U,所以这里再补充解释一下%u,%u:使用星期一作为一周的第一天,这个就不像上面一样了,这里并不是遇到每年的第一个周一算第 1 周,而是计算第一个周一之前的天数如果能超过3天(不包括3天),那么计算为第 1 周。否则就计算为第0周。

对于这样的跨年引发的周统计的问题,有没有更合理的解决方式呢?答案是有的,那就是MySQL的另外个两个格式符%X-%V

%X: 年,其中的星期日是周的第一天,4 位,与 %V 使用
%V: 周, (01-53) 星期日是一周的第一天,与 %X 使用

网上很少有关于%X,%V的使用方法的讲解,我也只是偶然中看到CSDN的一篇帖子才发现有这么个东西的存在,
%V 与 %U 一样的地方就是也是使用周日作为一周的开始,并且也是遇到第一个周日开始算作是第一周,但是这两个对这个下一年第一个周日之前的这几天处理不一样,%U是把下一年之前的几天算作第0周而%V是把下一年之前的几天算作上一年的最后一周,所以使用%X-%V这样的格式符就能解决图2的问题

这里也补充一下%v,%v与%u也有相似之处,就是同样适用周一作为 一周的开始,也同样计算每年第一个周一之前的
天数,如果>3天则记为第 1 周,不同是,如果≤3天,则算作上一年的最后一周

DATE_FORMAT(FROM_UNIXTIME(regTime,'%Y-%m-%d'),'%X-%V')

既然图2出现的跨年现象解决了,那么图1的现象是否也解决了呢?答案是不,为什么呢?分析图1和图2,我们可以看出,图2是因为上一年的最后一周跨到下一年了,所以导致有下一年的1.1这一周出现,但是图1不一样,16年的最后一周刚好到了12.31,并没有跨到17年,图一的17年第一周是从1.8开始的,1.1这一周没有显示出来。咦,这就奇怪啦,我是可以保证时间格式化是没有问题的,为什么还会出现这个问题呢?只有一个答案可以解释了,那么就是代码问题,如果是代码问题,那么就要追溯时间转换的方法上了,view代码。

问题就出现在这里了,因为转换周的原理是先求出1.1 + 周数 * 7 的日期,然后再减去1.1号距离第一个周日的天数 - 1,然后就得到某一周的开始的周日日期,具体公式为:(1.1 + 周数 * 7) - (1.1距离第一个周日的天数 - 1)

举例说明:

(1)2016年的1.1是周五,按周日为一周第一天这样来计算,周五就是一周的第6天,从1.1号开始,一周为7天。假设我们从第二周开始,1.1 + (周数 * 7)也就是 1.1 + 14 = 1.15,1.15 - (1.1距离第一个周日的天数 - 1)也就是1.15 - (6-1) = 1.10.所以1.10就是刚好是16年的第二个周的开始

(2)上面的例子对于大多数情况是适用的,但是如果一年的第一个周日刚好是1.1的话,上面的例子就会出现这样的情况,1.8 - (1 - 1) = 1.8,所以刚好1.1这一周的数据就被过滤掉了,所以如果刚好一年的第一个周日刚好是1.1的话,那么就应该是1.8 - 7 = 1.1,如下图所示

这是在处理项目跨年周统计的时候的总结,在第一点的时候也说到了MySQL的格式符问题,下面就想详细的解释一下MySQL几个格式符的问题

既然%Y表示年,%X也表示年,那么%X-%V是否可以写成%Y-%V呢?常用的表示年月的%Y-%m是否可以写成%X-%m呢?

# %X-%V
SELECT
DATE_FORMAT(FROM_UNIXTIME(regTime, '%Y-%m-%d'),'%X-%V'
) AS groupweek,count(distinct FROM_UNIXTIME(regTime, '%Y-%m-%d') ) from acctStatus
WHERE
regTime >UNIX_TIMESTAMP('2015-12-20 00:00:00') and regTime < UNIX_TIMESTAMP('2016-01-17 00:00:00')
AND regTime <> '' group by groupweek

结果为下图,查询正确

那如果写成%Y-%V,会发生什么情况呢?

# %Y-%V
SELECT
DATE_FORMAT(FROM_UNIXTIME(regTime, '%Y-%m-%d'),'%Y-%V'
) AS groupweek,count(distinct FROM_UNIXTIME(regTime, '%Y-%m-%d') ) from acctStatus
WHERE
regTime >UNIX_TIMESTAMP('2015-12-20 00:00:00') and regTime < UNIX_TIMESTAMP('2016-01-17 00:00:00')
AND regTime <> '' group by groupweek

结果如下图所示,会发现多出了一个诡异的2016年52周

分析原因:网上找不到合理的资料,我就根据我的理解分析下产生这个现象的原因,%Y的解释为年,%X的解释为年,其中的星期日是周的第一天,也就是说%Y是把1.1当做一年的第一天,所以%Y-%U会从第0周开始算起,而%X把第一个周日当做一周的第一天,也把第一个周日当做一年的第一天,所以和%V(周 (01-53) 星期日是一周的第一天,与 %X 使用刚好适用。而%Y与%V本身之间对于第一天的基准就是不同的,一起使用当然就会有问题,如果1.1距离第一个周日之间是没有数据的,那么我们不会发现这两种写法有什么区别,因为数据显示会是完全一样的。同理,来验证下%Y-%m与%X-%m的差别

# %Y-%m
SELECT
DATE_FORMAT(FROM_UNIXTIME(regTime, '%Y-%m-%d'),'%Y-%m'
) AS groupmonth,count(distinct FROM_UNIXTIME(regTime, '%Y-%m-%d') ) from acctStatus
WHERE
regTime >UNIX_TIMESTAMP('2015-12-20 00:00:00') and regTime < UNIX_TIMESTAMP('2016-01-17 00:00:00')
AND regTime <> '' group by groupmonth

结果如下图所示,查询结果正确

&nbsp;&nbsp;&nbsp;&nbsp;再来看%X-%m# %X-%m
SELECT
DATE_FORMAT(FROM_UNIXTIME(regTime, '%Y-%m-%d'),'%X-%m'
) AS groupmonth,count(distinct FROM_UNIXTIME(regTime, '%Y-%m-%d') ) from acctStatus
WHERE
regTime >UNIX_TIMESTAMP('2015-12-20 00:00:00') and regTime < UNIX_TIMESTAMP('2016-01-17 00:00:00')
AND regTime <> '' group by groupmonth

结果如下图所示,多出了一个2015-1,但是我们的查询条件却是从15.12开始的,也就是说2015-01这个数据是有问题的

总结

跨年的周统计要使用%X-%V,%Y要和%U、%m一起使用,%X要和%V一起使用,如果随意使用,就会产生错误,同理,
%x,%y,%u,%v也是一样的。

MySQL的跨年周统计问题(%X-%V)相关推荐

  1. mysql 周_MySQL的跨年周统计问题

    在mysql中,如果要查询的表中只有日期字段,但是业务需求要按照周分组,排序的话,mysql提供了多种方法: 1.date_format DATE_FORMAT(date, format) 函数根据f ...

  2. mysql每个月数据按天行转列_Mysql 中使用DATE_FORMAT函数按月、周统计数据

    项目中的统计报表作的很多,需求中有按周.月统计数据的.查看了Mysql的API,发现Date_format是格式化日期的,看了Date_format()的具体说明后就用这个函数按周统计,sql大致如下 ...

  3. mysql如何按季度分组统计_MySQL中按周、月、季、年分组统计

    根据日期,按照周.月.季.年对数据进行分组统计 一.前言 带着问题去学习,我觉得是一种非常有效的学习方法,不知下面的几个问题是否也是你所需要考虑的:被分组的日期是否连续.周是以周日为起始日,还是以周一 ...

  4. MySQL基础——按月/周/日分组统计数据

    MySQL基础--按月/周/日分组统计数据 在使用MySQL进行查询时,很多时候,我们需要按时间段进行统计,例如每周,每月的数据:这里我们需要使用到MySQL的关键词:DATE_FORMAT 1. 语 ...

  5. Mysql按日、周、月进行分组统计

    我们在用Mysql抽取数据时候,经常需要按照天.周.月等不同的粒度对数据进行分组统计.而我们的时间可能是"2017/12/5 0:0:0"这种准确的时间.所以在进行分组之前我们需要 ...

  6. mysql怎么对月份进行统计_MySQL如何按月份统计数据详解(转)

    这篇文章主要对MySQL按月份统计数据介绍:表finance有俩个字段如下date datemoney double(15,2)下面需要对表finance的2010年财务数据,按月进行统计 selec ...

  7. mysql获取当天每小时统计_详解mysql 获取某个时间段每一天、每一个小时的统计数据...

    获取每一天的统计数据 做项目的时候需要统对项目日志做分析,其中有一个需求是获取某个给定的时间段内,每一天的日志数据,比如说要获取从2018-02-02 09:18:36到2018-03-05 23:1 ...

  8. MySQL按 年 月 周 日统计表中的数据

    今天在做统计报表的时候,需要对表数据按年.月.周和日分别进行统计.统计用到了MySQL日期函数DATE_FORMAT.YEAR.QUARTER.MONTH和WEEK,本文就这些函数的使用和功能实现进行 ...

  9. mysql数据入库时间的统计_MySQL按时间统计数据的方法总结

    在做数据库的统计时,经常会需要根据年.月.日来统计数据,然后配合echarts来制作可视化效果. 数据库:MySQL 思路 按照时间维度进行统计的前提是需要数据库中有保留时间信息,建议是使用MySQL ...

最新文章

  1. Python的XML-RPC学习
  2. java中的浮点计算
  3. 如何从Amazon API Gateway将查询字符串或路由参数传递到AWS Lambda
  4. Sublime Text 3 插件的安装、升级和卸载
  5. 超简易复制Model对象(为后续备忘录设计模式博文做铺垫)
  6. linux 文件解压 压缩
  7. iOS Hacker dumpdecrypted脱壳
  8. gradle 编译java_Java的Gradle依赖关系,使用编译还是实现?
  9. html2canvas图片位移_html2canvas html截图插件图片放大清晰度处理方案,支撑恣意放大倍数,处理原插件图片偏移题目...
  10. 经典面试题(42):以下代码将输出的结果是什么?
  11. matlab磁铁模拟,用matlab-模拟环形磁铁的磁场分布
  12. 第35次Scrum会议(11/23)【欢迎来怼】
  13. c语言中tail和head的用法,tail 与head用法
  14. 阿里巴巴CTO程立:践行“好科技”,我们正在做这些事
  15. 中国省份城市0-N编号
  16. 谈谈“天轰穿”老师对VS2005的视频讲座
  17. NVT平台model sensor配置
  18. 基于matlab的控制系统仿真题,MATLAB与控制系统仿期末考试试卷真
  19. 微信小程序连接mysql
  20. 【Python二级等考大题】星座三问

热门文章

  1. sqlhelper类 java_SQLHelper类(Java版)
  2. Abaqus 三维多面体骨料 随机多面体3D 无干涉多面体骨料模型
  3. html5小圆点,HTML5的Canvas实现小圆点在屏幕内跑动
  4. 阿里P6+的Java架构班课程招生,仅限20名!
  5. 华为手机什么时候更新鸿蒙系统_华为手机升级鸿蒙系统时间确认!这3部华为手机的机会最大...
  6. REVV Motorsport:SHRD 即将到来
  7. 怎么调图片分辨率?怎么改图片分辨率?
  8. Kali Linux渗透测试——MSF实战篇(一)
  9. 返回列表结果数据再请求详情
  10. 牛批!简单几步,无坑部署最小化 K8S 集群