前几天帮同事修改了一个bug,这个bug是怎么造成的呢。先来看需求,系统需要统计某个月份的数据。很简单的一个需求。
同事的写的MySQL语句
SELECT
REPLACE(FORMAT(sum(count_value),2), ',', '') as value,
<if test="type == 'day'">
count_date as `time`
</if>
<if test="type == 'hour'">
count_time as `time`
</if>
<if test="type == 'month'">
DATE_FORMAT(count_date, '%Y-%m') as `time`
</if>
<if test="type == 'year'">
DATE_FORMAT(count_date, '%Y') as `time`
</if>
FROM
device_value
WHERE count_date BETWEEN #{startTime} AND #{endTime}
and node_id in
<foreach collection="nodeIds" separator="," open="(" close=")" item="nodeId">
#{nodeId}
</foreach>
GROUP BY
<if test="type == 'day'">
count_date
</if>
<if test="type == 'hour'">
count_time
</if>
<if test="type == 'month'">
DATE_FORMAT(count_date, '%Y-%m')
</if>
<if test="type == 'year'">
DATE_FORMAT(count_date, '%Y')
</if>
<if test="type == 'day'">
order by count_date
</if>
<if test="type == 'hour'">
order by count_time
</if>
<if test="type == 'month'">
order by DATE_FORMAT(count_date, '%Y-%m')
</if>
sql也很简单,就是通过传递不同的type从时间的不同角度去统计出device_value这张表中的在startTime和endTime这时间段内的value值之和。
然后就是Java代码了,具体就不展示了。总结来说他如果要查询六月的数据,他就是将前端传递过来的2024-06这样一个字符串拼接上一个01和31。然后就传递到xml中查询。运行时MySQL代码:
SELECT REPLACE
( FORMAT( sum( count_value ), 2 ), ',', '' ) AS VALUE,
count_date AS `time`
FROM
device_everyday_count_value
WHERE
count_date BETWEEN '2024-06-01' AND '2024-06-31'
AND node_id IN ( 'KTXTYCNH' )
GROUP BY
count_date
ORDER BY
count_date
看一下查询结果
你会发现是没有结果的,实际上6月是有数据的,但是6月确实没有31号的只有30号,我将他写的31改为30后再此运行同样MySQL代码结果如下
讲到这里大家应该也发现了代码问题之处了,就是日常超出了范围导致的查询没有结果。在前端展示的时候并不会报错,只是小于31天的数据是没有值的。
最后我将他的Java代码在传递日期的时候给修改了,通过LocalTime来获取某一年的某一月的日期的开始以及结束日期。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate startTime = LocalDate.parse(date, formatter);
String endDate = date;
if("month".equals(dateType)){
date = startTime.with(TemporalAdjusters.firstDayOfMonth()).toString();
endDate = startTime.with(TemporalAdjusters.lastDayOfMonth()).toString();
}else if("year".equals(dateType)){
date = startTime.with(TemporalAdjusters.firstDayOfYear()).toString();
endDate = startTime.with(TemporalAdjusters.lastDayOfYear()).toString();
}
事后我有问过他,我问他当时怎么想的,为什么会当时会这样写代码呢?
他跟我说:我写的时候觉得没什么不对啊,我当时觉得每个月的月份开始日期都是01,然后结束最大就是31我拼接出来的日期就算是6月只有30天但是我31也没什么问题也包含了30号的日期,并不会遗漏数据所以就这么写了。
然后我又问他:你当时写的时候没去测一下这种写法在特殊的月份的时候查询会不会出现问题呢。
他说:我没有,我当时写来并没有调用接口的时候并没有报错,并且有数据我就没管了。
他当时写的时候是5月份所以并不会有什么问题。至此我也不好再多说什么
我只能说善用现有的工具,别写魔法数字和魔法字符串。
路漫漫其修远兮,吾将上下而求索!!!