需求背景

(读者可略过)
司机每天从早到晚都会去到不同的自动售货机上补货,而且补货次数和路线等也是因人而异,补货依据是由系统优化并指派。但是目前系统还无法实施有效指挥和优良的补货策略,司机的补货活动因此变得较为随意和散漫。为了有效跟踪司机补货,计算司机补货效率,也为了便于HR月底计算司机绩效,因此提出需求需要根据司机补货记录数据阶段性地计算出补货情况统计值。

需求中指出,按天按司机手机号为统计维度,需要统计司机当天总共补货的机器数(相同机器补多次也只能算一台机器);统计司机当天总共补货的次数(自然小时内补了货只算一次);统计司机当天补货时长,即统计从司机第一次补货开始时间至当天最后一次补货结束时间为止之间的时长。

本次需求背景来自于售货机供应链系统。

需求实现

项目实现基于SpringBoot基础框架,搭配BeetlSql实现ORM,以下SQL均是基于此的MarkDown格式语句,另基于JDK 1.8,不过针对当前主题,以上环境参数均可忽略。

1、统计司机补货总机器数

 SELECT
t.count_date,
t.phone_num,
count(1) AS 'totalCount'
FROM
(
SELECT
DATE_FORMAT(supplement_time, "%Y-%m-%d") AS count_date,
phone_num,
device_id
FROM
cardslot_supplement_record
WHERE 1 = 1
@if(!isEmpty(startDate)){
AND supplement_time > #startDate#
@}
@if(!isEmpty(endDate)){
AND supplement_time < CONCAT(#endDate#, " 23:59:59")
@}
GROUP BY
count_date,
phone_num,
device_id
) t
GROUP BY
t.count_date,
t.phone_num

2、统计司机补货总次数

 SELECT
t.count_date,
t.phone_num,
count(1) 'totalCount'
FROM
(
SELECT
DATE_FORMAT(supplement_time, "%Y-%m-%d") AS count_date,
phone_num,
DATE_FORMAT(
supplement_time,
"%Y-%m-%d %H"
) AS 'count_date_hour'
FROM
cardslot_supplement_record
WHERE 1 = 1
@if(!isEmpty(startDate)){
AND supplement_time > #startDate#
@}
@if(!isEmpty(endDate)){
AND supplement_time < CONCAT(#endDate#, " 23:59:59")
@}
GROUP BY
count_date,
phone_num,
count_date_hour
) t
GROUP BY
t.count_date,
t.phone_num

3、统计司机补货总时长

 SELECT
DATE_FORMAT(supplement_time, "%Y-%m-%d") AS 'count_date',
phone_num,
SEC_TO_TIME(
UNIX_TIMESTAMP(max(supplement_end_time)) - UNIX_TIMESTAMP(min(supplement_start_time))
) AS 'diffTime'
FROM
cardslot_supplement_record
WHERE 1 = 1
@if(!isEmpty(startDate)){
AND supplement_time > #startDate#
@}
@if(!isEmpty(endDate)){
AND supplement_time < CONCAT(#endDate#, " 23:59:59")
@}
GROUP BY
count_date,
phone_num

接收查询结果的POJO类如下:

 // 司机手机号码
private String phoneNum; // 统计日期
private String countDate; // 统计数量
private Integer totalCount; // 时间差
private String diffTime;

在做测试执行查询时,前两个SQL执行都正常,最后的SQL在IDE工具中执行也是OK的,但是在Java程序中运行时,出现了如下异常:

 Sql异常┏━━━━━ Debug [cardslotSupplementRecord.queryDriverReplenishmentD...] ━━━
┣ SQL: SELECT DATE_FORMAT(supplement_time, "%Y-%m-%d") AS 'count_date', phone_num, SEC_TO_TIME( UNIX_TIMESTAMP(max(supplement_end_time)) - UNIX_TIMESTAMP(min(supplement_start_time)) ) AS 'diffTime' FROM cardslot_supplement_record WHERE 1 = 1 AND supplement_time > ? AND supplement_time < CONCAT(?, " 23:59:59") GROUP BY count_date, phone_num
┣ 参数: [2018-08-01 00:00:00, 2018-09-06 23:59:59]
┣ 位置: com.bluepay.operation.service.impl.DriverReplenishmentSummaryExportServiceImpl.executeQueryAndExport(DriverReplenishmentSummaryExportServiceImpl.java:46)
┗━━━━━ Debug [ ERROR:Bad format for Time '162:30:34' in column 3] ━━━ [ERROR] 2018-09-06 16:22:56,856 -2592- [pool-3-thread-1] [com.bluepay.operation.service.export.impl.ExportBlockingQueueServiceImpl] 下载任务出现异常
org.beetl.sql.core.BeetlSQLException: java.sql.SQLException: Bad format for Time '162:30:34' in column 3
at org.beetl.sql.core.SQLScript.select(SQLScript.java:403)
at org.beetl.sql.core.SQLManager.select(SQLManager.java:475)
at org.beetl.sql.core.SQLManager.select(SQLManager.java:461)
at org.beetl.sql.core.mapper.SelectMapperInvoke.call(SelectMapperInvoke.java:29)
at org.beetl.sql.core.mapper.MapperJavaProxy.invoke(MapperJavaProxy.java:162)
at org.beetl.sql.core.mapper.MapperJava8Proxy.invoke(MapperJava8Proxy.java:92)
at com.sun.proxy.$Proxy109.queryDriverReplenishmentDuration(Unknown Source)
at com.bluepay.operation.service.impl.DriverReplenishmentSummaryExportServiceImpl.executeQueryAndExport(DriverReplenishmentSummaryExportServiceImpl.java:46)
at com.bluepay.operation.service.export.impl.ExportBlockingQueueServiceImpl$1$1.run(ExportBlockingQueueServiceImpl.java:75)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.sql.SQLException: Bad format for Time '162:30:34' in column 3
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:965)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:898)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:887)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:861)
at com.mysql.jdbc.ResultSetImpl.getTimeFromString(ResultSetImpl.java:5511)
at com.mysql.jdbc.ResultSetImpl.getStringInternal(ResultSetImpl.java:5284)
at com.mysql.jdbc.ResultSetImpl.getString(ResultSetImpl.java:5138)
at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:951)
at com.alibaba.druid.wall.WallFilter.resultSet_getString(WallFilter.java:1014)
at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:947)
at com.alibaba.druid.filter.stat.StatFilter.resultSet_getString(StatFilter.java:938)
at com.alibaba.druid.filter.FilterChainImpl.resultSet_getString(FilterChainImpl.java:947)
at com.alibaba.druid.proxy.jdbc.ResultSetProxyImpl.getString(ResultSetProxyImpl.java:685)
at com.alibaba.druid.pool.DruidPooledResultSet.getString(DruidPooledResultSet.java:111)
at org.beetl.sql.core.mapping.type.StringTypeHandler.getValue(StringTypeHandler.java:46)
at org.beetl.sql.core.mapping.BeanProcessor.createBean(BeanProcessor.java:320)
at org.beetl.sql.core.mapping.BeanProcessor.toBeanList(BeanProcessor.java:177)
at org.beetl.sql.core.SQLScript.mappingSelect(SQLScript.java:438)
at org.beetl.sql.core.SQLScript.select(SQLScript.java:385)
... 13 common frames omitted

出现这个问题之后,很疑惑,确定在SQL IDE(Navicat)中执行没有任何异常,从异常日志中得出是第三列也就是时间差计算中出现了不符合时间格式的异常,接下来Google一下。

问题解答

Google之后立马找到了答案,MySQL针对此问题专门开了CASE。有外国网友遇到了同样的问题:基于Java开发,在MySQL数据库上使用TIMEDIFF计算时间差,然后使用String类型接收结果值,报了如我一般的异常。当时给出的解决建议是,在计算到时间差异之后,转换为字符串格式,这样程序执行就不会报错了。

这里就有一个疑问了,为何直接这样输出就不行呢?

这其中隐藏着一个类型转换的问题。MySQL中的TIMEDIFF(时间差计算那一类函数)函数计算的结果是一个“xx:xx:xx”(xx小时:xx分钟:xx秒钟)时间格式的值,是一个时间长度(或者说跨度)的且以“%H:%m:%d”格式表现出来的值,以此输出到Java程序中之后,JDK自做聪明将其转换为了“java.sql.Time”类型,很明显这是一个时间类型,时间格式中小时的最大值是23,一旦我们的结果超过这个数字,自然就报错了,在这个类的底层,我们发现它就是通过“xx:xx:xx”格式的长度来进行判断和处理的,一旦超出这个长度就会抛出异常,因为格式显然不符合了。

所以针对这个问题比较恰当的处理方式,就是在SQL结果输出之前就将其转换为字符串格式,这样Java程序获取到结果之后就不会隐式对其进行转换操作了,最后程序能够得以正常执行。

解决方案参考: https://bugs.mysql.com/bug.php?id=70892

最后根据如上描述,我将SQL进行了如下修改,执行通过:

 SELECT
DATE_FORMAT(supplement_time, "%Y-%m-%d") AS 'count_date',
phone_num,
CONCAT(SEC_TO_TIME(
UNIX_TIMESTAMP(max(supplement_end_time)) - UNIX_TIMESTAMP(min(supplement_start_time))
), '') AS 'diffTime'
FROM
cardslot_supplement_record
WHERE 1 = 1
@if(!isEmpty(startDate)){
AND supplement_time > #startDate#
@}
@if(!isEmpty(endDate)){
AND supplement_time < CONCAT(#endDate#, " 23:59:59")
@}
GROUP BY
count_date,
phone_num

问题总结

MySQL中诸如TIMEDIFF(时间差计算那一类函数)一类函数计算出的结果可能都是“hh:MM:ss”格式的值,如果想在Java程序中以String类型来接受处理,那么一定要事先确定这个以“时间格式”来表示的值,是否超过了时间表达的最大范围值,如果不能确定的,都最好在输出到程序中执行之前,对其进行字符串化再输出,以免在后续程序执行时抛出异常。

SQL函数TIMEDIFF在Java程序中使用报错的问题分析的更多相关文章

  1. 小程序api使用报错

    小程序连接api报错: 如若已在管理后台更新域名配置,请刷新项目配置后重新编译项目,操作路径:“项目-域名信息” 解决办法如下: 点击设置—-项目设置—勾选如下选项即可解决

  2. Linux上从Java程序中调用C函数

    原则上来说,"100%纯Java"的解决方法是最好的,但有些情况下必须使用本地方法.特别是在以下三种情况: 需要访问Java平台无法访问的系统特性和设备: 通过基准测试,发现Jav ...

  3. Java程序中解决数据库超时与死锁

    Java程序中解决数据库超时与死锁 2011-06-07 11:09 佚名 帮考网 字号:T | T   Java程序中解决数据库超时与死锁,每个使用关系型数据库的程序都可能遇到数据死锁或不可用的情况 ...

  4. 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案

    方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...

  5. java程序中获取kerberos登陆hadoop

    本文由作者周梁伟授权网易云社区发布. 一般我们在使用kbs登陆hadoop服务时都直接在shell中调用kinit命令来获取凭证,这种方式简单直接,只要获取一次凭证之后都可以在该会话过程中重复访问.但 ...

  6. Derby安装,创建数据库,在Java程序中使用Derby

    1,下载并安装Derby: 下载地址:http://db.apache.org/derby /derby_downloads.html,下载最新版本. 我用的是10.5.3.0. 解压缩到任意文件夹, ...

  7. 在java程序中使用JDBC连接mysql数据库

    在java程序中我们时常会用到数据库中的数据或操作数据库中的数据,如果java程序没有和我们得数据库连接,就不能实现在java程序中直接操作数据库.使用jdbc就能将java程序和数据库连起来,此时我 ...

  8. 如何在java程序中调用linux命令或者shell脚本

    转自:http://blog.sina.com.cn/s/blog_6433391301019bpn.html 在java程序中如何调用linux的命令?如何调用shell脚本呢? 这里不得不提到ja ...

  9. Java程序中调用Python脚本的方法

    在程序开发中,有时候需要Java程序中调用相关Python脚本,以下内容记录了先关步骤和可能出现问题的解决办法. 1.在Eclipse中新建Maven工程: 2.pom.xml文件中添加如下依赖包之后 ...

随机推荐

  1. 几个重要的 ASM Disk Groups 参数

    几个重要的Disk group 属性: 1. ACCESS_CONTROL.ENABLED该属性用来控制某个disk group 上ASM FILE Access Control. 该参数有2个值:t ...

  2. Python手记

    字符串的拼接 1.“+”,如果是字符和数字相连,要使用str()函数对于数字进行字符转化: 2.join() 3.",",链接的两个字符串之间用空格做关联 4.占位符 tmp += ...

  3. saltstack syndic安装配置使用

    salt-syndic是做神马的呢?如果大家知道zabbix proxy的话那就可以很容易理解了,syndic的意思为理事,其实如果叫salt-proxy的话那就更好理解了,它就是一层代理,如同zab ...

  4. UDK性能优化

    转自:http://www.cnblogs.com/NEOCSL/p/3320510.html 优化问题有很多内容可讲,涉及林林总总.今天我总结一下优化注意的地方. 1.从AnimTree和Skele ...

  5. win10 设备摄像头,麦克风,【隐私】权限

    win10 因为隐私问题, 把mic,摄像头, 定位功能关闭,  之后调用USB摄像头的时候,忘了这个, 接口API 一直返回调用失败,[不能创建视频捕捉过滤器 hr=0x80070005] => ...

  6. javascript 日期月份加减

    项目中需要用到,自己写了一个.javascript日期按月加减 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xh ...

  7. [codeforces219D]Choosing Capital for Treeland树形dp

    题意:给出一棵树,带有向边,找出某个点到达所有点需要反转的最少的边. 解题关键:和求树的直径的思路差不多,将求(父树-子树)的最大值改为求特定值.依然是两次dfs,套路解法. 对树形dp的理解:树形d ...

  8. Http协议-报文

    2013的双12即将到来,网上购物是大家所熟悉的.看中小米电视时,可以先下订单然后再付款,电商根据订单将小米电视正确安全的送达给我们.包裹包含电视的基本信息及电视的使用说明书,使我们能够初步的了解它的 ...

  9. Hash表的实现

    #include "stdafx.h" #include <iostream> #include <exception> using namespace s ...

  10. 《Java多线程编程核心技术》读后感(八)

    不使用等待/通知机制实现线程间通信 使用sleep()结合while(true)死循环来实现多个线程间通信 package Third; import java.util.ArrayList; imp ...