mysql查詢每小時數據和上小時數據的差值

一、前言

      需求是獲取某個時間範圍內每小時數據和上小時數據的差值以及比率。本來以爲會是一個很簡單的sql,結果思考兩分鐘發現並不簡單,網上也沒找到參考的方案,那就只能自己慢慢分析了。

      剛開始沒思路,就去問DBA同學,結果DBA說他不會,讓我寫php腳本去計算,,這就有點過分了,我只是想臨時查個數據,就不信直接用sql查不出來,行叭,咱們邊走邊試。

博主這裏用的是笨方法實現的,各位大佬要是有更簡單的方式,請不吝賜教,評論區等你!

mysql版本:

mysql> select version();
+---------------------+
| version()           |
+---------------------+
| 10.0.22-MariaDB-log |
+---------------------+
1 row in set (0.00 sec)

二、查詢每個小時和上小時的差值

1、拆分需求

這裏先分開查詢下,看看數據都是多少,方便後續的組合。

(1)獲取每小時的數據量

這裏爲了方便展示,直接合並了下,只顯示01-12時的數據,並不是bug。。

select count(*) as nums,date_format(log_time,'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days;
+-------+---------------+
| nums  | days          |
+-------+---------------+
| 15442 | 2020-04-19 01 |
| 15230 | 2020-04-19 02 |
| 14654 | 2020-04-19 03 |
| 14933 | 2020-04-19 04 |
| 14768 | 2020-04-19 05 |
| 15390 | 2020-04-19 06 |
| 15611 | 2020-04-19 07 |
| 15659 | 2020-04-19 08 |
| 15398 | 2020-04-19 09 |
| 15207 | 2020-04-19 10 |
| 14860 | 2020-04-19 11 |
| 15114 | 2020-04-19 12 |
+-------+---------------+

(2)獲取上小時的數據量

select count(*) as nums1,date_format(date_sub(date_format(log_time,'%Y-%m-%d %h'),interval -1 hour),'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days;
+-------+---------------+
| nums1 | days          |
+-------+---------------+
| 15114 | 2020-04-19 01 |
| 15442 | 2020-04-19 02 |
| 15230 | 2020-04-19 03 |
| 14654 | 2020-04-19 04 |
| 14933 | 2020-04-19 05 |
| 14768 | 2020-04-19 06 |
| 15390 | 2020-04-19 07 |
| 15611 | 2020-04-19 08 |
| 15659 | 2020-04-19 09 |
| 15398 | 2020-04-19 10 |
| 15207 | 2020-04-19 11 |
| 14860 | 2020-04-19 12 |
+-------+---------------+

注意:

1)獲取上小時數據用的是date_sub()函數,date_sub(日期,interval -1 hour)代表獲取日期參數的上個小時,具體參考手冊:https://www.w3school.com.cn/sql/func_date_sub.asp
2)這裏最外層嵌套了個date_format是爲了保持格式和上面的一致,如果不加這個date_format的話,查詢出來的日期格式是:2020-04-19 04:00:00的,不方便對比。

2、把這兩份數據放到一起看看

select nums ,nums1,days,days1 
from 
(select count(*) as nums,date_format(log_time,'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days) as m,
(select count(*) as nums1,date_format(date_sub(date_format(log_time,'%Y-%m-%d %h'),interval -1 hour),'%Y-%m-%d %h') as days1 from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days1) as n;

+-------+-------+---------------+---------------+
| nums  | nums1 | days          | days1         |
+-------+-------+---------------+---------------+
| 15442 | 15114 | 2020-04-19 01 | 2020-04-19 01 |
| 15442 | 15442 | 2020-04-19 01 | 2020-04-19 02 |
| 15442 | 15230 | 2020-04-19 01 | 2020-04-19 03 |
| 15442 | 14654 | 2020-04-19 01 | 2020-04-19 04 |
| 15442 | 14933 | 2020-04-19 01 | 2020-04-19 05 |
| 15442 | 14768 | 2020-04-19 01 | 2020-04-19 06 |
| 15442 | 15390 | 2020-04-19 01 | 2020-04-19 07 |
| 15442 | 15611 | 2020-04-19 01 | 2020-04-19 08 |
| 15442 | 15659 | 2020-04-19 01 | 2020-04-19 09 |
| 15442 | 15398 | 2020-04-19 01 | 2020-04-19 10 |
| 15442 | 15207 | 2020-04-19 01 | 2020-04-19 11 |
| 15442 | 14860 | 2020-04-19 01 | 2020-04-19 12 |
| 15230 | 15114 | 2020-04-19 02 | 2020-04-19 01 |
| 15230 | 15442 | 2020-04-19 02 | 2020-04-19 02 |
| 15230 | 15230 | 2020-04-19 02 | 2020-04-19 03 |

      可以看到這樣組合到一起是類似於程序中的嵌套循環效果,相當於nums是外層循環,nums1是內存循環。循環的時候先用nums的值,匹配所有nums1的值。類似於php程序中的:

foreach($arr as $k=>$v){
    foreach($arr1 as $k1=>$v1){

    }
}

      既然如此,那我們是否可以像平時寫程序的那樣,找到兩個循環數組的相同值,然後進行求差值呢?很明顯這裏的日期是完全一致的,可以作爲對比的條件。

3、使用case …when 計算差值

select (case  when days = days1 then (nums - nums1) else 0 end) as diff
from 
(select count(*) as nums,date_format(log_time,'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days) as m,
(select count(*) as nums1,date_format(date_sub(date_format(log_time,'%Y-%m-%d %h'),interval -1 hour),'%Y-%m-%d %h') as days1 from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days1) as n;

效果:
+------+
| diff |
+------+
|  328 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
|    0 |
| -212 |
|    0 |
|    0 

      可以看到這裏使用case..when實現了當兩個日期相等的時候,就計算差值,近似於php程序的:

	foreach($arr as $k=>$v){
    foreach($arr1 as $k1=>$v1){
        if($k == $k1){
            //求差值
        }
    }
}

      結果看到有大量的0,也有一部分計算出的結果,不過如果排除掉這些0的話,看起來好像有戲的。

4、過濾掉結果爲0 的部分,對比最終數據

      這裏用having來對查詢的結果進行過濾。having子句可以讓我們篩選成組後的各組數據,雖然我們的sql在最後面沒有進行group by,不過兩個子查詢裏面都有group by了,理論上來講用having來篩選數據是再合適不過了,試一試

select (case  when days = days1 then (nums1 - nums) else 0 end) as diff
from 
(select count(*) as nums,date_format(log_time,'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days) as m,
(select count(*) as nums1,date_format(date_sub(date_format(log_time,'%Y-%m-%d %h'),interval -1 hour),'%Y-%m-%d %h') as days1 from test where 1 and log_time >='2020-04-19 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days1) as n having diff <>0;

結果:
+------+
| diff |
+------+
| -328 |
|  212 |
|  576 |
| -279 |
|  165 |
| -622 |
| -221 |
|  -48 |
|  261 |
|  191 |
|  347 |
| -254 |
+------+

      這裏看到計算出了結果,那大概對比下吧,下面是手動列出來的部分數據:

當前小時和上個小時的差值: 當前小時  -上個小時

本小時	上個小時		差值
15442	15114		-328
15230	15442		212
14654	15230		576
14933	14654		-279
14768	14933		165

      可以看到確實是成功獲取到了差值。如果要獲取差值的比率的話,直接case when days = days1 then (nums1 - nums)/nums1 else 0 end 即可。

5、獲取本小時和上小時數據的降幅,並展示各個降幅範圍的個數

      在原來的case..when的基礎上引申一下,繼續增加條件劃分範圍,並且最後再按照降幅範圍進行group by求和即可。這個sql比較麻煩點,大家有需要的話可以按需修改下,實際測試是可以用的。

select  case  
when days = days1 and (nums1 - nums)/nums1 < 0.1 then 0.1
when days = days1 and (nums1 - nums)/nums1 > 0.1 and (nums1 - nums)/nums1 < 0.2 then 0.2
when days = days1 and (nums1 - nums)/nums1 > 0.2 and (nums1 - nums)/nums1 < 0.3 then 0.3
when days = days1 and (nums1 - nums)/nums1 > 0.3 and (nums1 - nums)/nums1 < 0.4 then 0.4
when days = days1 and (nums1 - nums)/nums1 > 0.4 and (nums1 - nums)/nums1 < 0.5 then 0.5
when days = days1 and (nums1 - nums)/nums1 > 0.5  then 0.6
  else 0 end as diff,count(*) as diff_nums
from 
(select count(*) as nums,date_format(log_time,'%Y-%m-%d %h') as days from test where 1 and log_time >='2020-03-20 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days) as m,
(select count(*) as nums1,date_format(date_sub(date_format(log_time,'%Y-%m-%d %h'),interval -1 hour),'%Y-%m-%d %h') as days1 from  test where 1 and log_time >='2020-03-20 00:00:00' and log_time <= '2020-04-20 00:00:00' group by days1) as n group by diff having diff >0;

結果:

+------+-----------+
| diff | diff_nums |
+------+-----------+
|  0.1 |       360 |
|  0.2 |        10 |
|  0.3 |         1 |
|  0.4 |         1 |
+------+-----------+

三、總結

1、 sql其實和程序代碼差不多,拆分需求一步步組合,大部分需求都是可以實現的。一開始就慫了,那自然是寫不出的。
2、 不過複雜的計算,一般是不建議用sql來寫,用程序寫會更快,sql越複雜,效率就會越低。
3、 DBA同學有時候也不靠譜,還是要靠自己啊

end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章