Mysql:GROUP BY自定义日期间隔

时间:2018-10-01 12:15:02

标签: mysql sql

状况:每分钟X能量设备节省其消耗量的表。我必须计算某天某台设备的每日消耗量(以小时间隔,00-01-02-03-04 ..... 23)(以创建一个简单的消耗量每小时图)。

id | date                |  total | id_device
---------------------------------------------
0  | 2018-10-01 00:01:00 | 100    | 1
---------------------------------------------
1  | 2018-10-01 00:01:00 | 101    | 2
---------------------------------------------
2  | 2018-10-01 00:02:00 | 110    | 1
---------------------------------------------
3  | 2018-10-01 00:02:00 | 105    | 2
---------------------------------------------
.. | 2018-10-01 23:59:00 | 200    | 1
---------------------------------------------
.. | 2018-10-01 23:59:00 | 1000   | 2

我这样做是为了计算每小时消费

SELECT CONCAT(IF(HOUR(`date`) < 10 , '0','') , HOUR(`date`)) AS `HH`, (MAX(`total`) - MIN(`total`)) AS `total` 
FROM `mytable`
WHERE `date` BETWEEN DATE_FORMAT(?, '%Y-%m-%d 00:00:00') AND DATE_FORMAT(?, '%Y-%m-%d 23:59:59') AND id_device = ?
GROUP BY `HH`

结果

HH | total
----------
00 | 100
01 | ...
.. | ...
23 | ...

此查询正确返回总计(如果一个小时没有保存,则不会显示在查询中,没问题)。

但是GROUP BY的工作方式如下

  • 00间隔:00:00-00:59
  • 01时间间隔:01:00-01:59
  • 02间隔:02:00-02:59
  • ....
  • 23间隔:23:00-23:59

但是我需要这个,否则时间计算不正确

  • 00时间间隔:23:59(前一天)-00:59
  • 01时间间隔:00:59-01:59
  • 02时间间隔:01:59-02:59
  • ....
  • 23间隔:22:59-23:59

是否可能有这种间隔?


PS:对于情况00,我知道我必须从前一天记录的最后一个值开始更改搜索,但是现在这不是我的问题。我会那样做:

WHERE 'date' BETWEEN 
  COALESCE((SELECT 'date' FROM 'mytable' WHERE 'date' < DATE_FORMAT(?, '%Y-%m-%d 00:00:00') ORDER BY 'date' DESC LIMIT 1), DATE_FORMAT(?, '%Y-%m-%d 00:00:00')) 
  AND DATE_FORMAT(?, '%Y-%m-%d 23:59:59')

UPDATE:DB Fiddle示例。一共有3台设备,每台设备都有5天的记录。

https://www.db-fiddle.com/f/ddvVguupi74TQjQ6yWJUzB/3

实际结果(id_device 1,日期为2018年10月3日):

HH  total
00  354
01  354
02  354
03  354
04  354
05  354
06  354
07  354
08  354
09  354
10  354
11  354
12  354
13  354
14  354
15  354
16  354
17  354
18  354
19  354
20  354
21  354
22  354
23  354

预期结果

HH  total
00  360
01  360
02  360
03  360
04  360
05  360
06  360
07  360
08  360
09  360
10  360
11  360
12  360
13  360
14  360
15  360
16  360
17  360
18  360
19  360
20  360
21  360
22  360
23  360

3 个答案:

答案 0 :(得分:1)

  • 使用MINUTE()函数,您可以确定分钟值是否为59。
  • 如果等于59,则可以将要考虑的小时值加1。
  • 改为使用LPAD()函数添加前导“ 0”,最大字符串大小为2。

您可以使用以下方法确定HH

LPAD(IF(
        MINUTE(`date`) = 59, 
        HOUR(`date`) + 1, 
        HOUR(`date`)
       ), 
     2, 
     '0'
    ) AS `HH`

但是,当前的问题是您在59分钟时有两组冲突的行。 例如:0102组中必须考虑在 01:59:23 处的行。 一个简单的小组是不可能的。因此,一种方法是考虑两种不同的方法 Select条语句获取一行的HH值。一次选择将考虑原始小时值, 另一个人会考虑在hour + 1中使用59分钟。但是然后,我们将重复 其他所有分钟(第59分钟除外)。这个重复的问题可以通过解决 利用Union语句。

然后,您可以将 Unionized 结果集用作Derived table,并执行简单的Group By

因此,您可以尝试以下操作(当一天发生变化时,您仍然必须处理一些极端情况):

SELECT 
  dt.HH, 
  (MAX(dt.total) - MIN(dt.total)) AS total 
FROM
    (
      SELECT LPAD(IF(MINUTE(t1.date) = 59, HOUR(t1.date) + 1, HOUR(t1.date)), 2, '0') AS HH, 
             t1.total 
      FROM mytable AS t1
      WHERE t1.date BETWEEN DATE_FORMAT(?, '%Y-%m-%d 00:00:00') AND
                            DATE_FORMAT(?, '%Y-%m-%d 23:59:59')

      UNION 

      SELECT LPAD(HOUR(t2.date), 2, '0') AS HH, 
             t2.total 
      FROM mytable AS t2
      WHERE t2.date BETWEEN DATE_FORMAT(?, '%Y-%m-%d 00:00:00') AND
                            DATE_FORMAT(?, '%Y-%m-%d 23:59:59')
    ) AS dt
GROUP BY dt.HH

答案 1 :(得分:0)

只需将您的date列移动一分钟即可:

SELECT CONCAT(IF(HOUR(`date`) < 10 , '0','') , HOUR(`date`)) AS `HH`, (MAX(`total`) - MIN(`total`)) AS `total` 
FROM (
    SELECT DATE_ADD(`date`, INTERVAL 1 MINUTE) `date`, `total`
    FROM `mytable`
    WHERE `date` BETWEEN DATE_FORMAT(?, '%Y-%m-%d 00:00:00') AND DATE_FORMAT(?, '%Y-%m-%d 23:59:59')
) a GROUP BY `HH`

答案 2 :(得分:0)

这很简单:

SELECT 
  DATE_ADD(DATE(`date`), INTERVAL HOUR(`date`) HOUR) as date_hour,
  MAX(`value`) -
  COALESCE((SELECT MAX(ta.`value`) FROM test ta WHERE ta.id < MIN(t.id)),0) as this_hour_consumption
FROM
  test t
GROUP BY
  DATE_ADD(DATE(`date`), INTERVAL HOUR(`date`) HOUR);

工作原理:

  • 从时间中删除分钟和秒,将每个日期减少到仅日期和小时

  • 查找该小时内的最大消费量

  • 查找ID小于小时中的最小ID的最大消耗值(即前一小时的最大消耗值

  • 从上一小时的最大消费中减去该小时的最大消费,得出该小时的消费

还有其他方法可以给这只猫蒙皮:

SELECT 
  DATE_ADD(DATE(a.`date`), INTERVAL HOUR(a.`date`) HOUR) the_hour,
  SUM(a.`value` - b.`value`) sum_consumption_this_hour
FROM 
  test a INNER JOIN test b on a.id = b.id + 1
GROUP BY
  DATE_ADD(DATE(a.`date`), INTERVAL HOUR(a.`date`) HOUR);

此方法的工作原理:

  • 在id = id-1上将数据连接到自身(为您提供“此”表和“上一个”表)
  • 计算连续记录(this.value-previous.value)之间的消耗量变化
  • 然后按小时将它们分组(与上述方法相同,将datetime减少为一个日期,然后加上HOUR(s))并将它们相加

编辑:

修改了第一个查询以使用修改后的样本数据:

SELECT 
  LPAD(HOUR(`date`), 2, '0') as date_hour,
  MAX(`total`) -
  COALESCE((SELECT MAX(ta.`total`) FROM test ta WHERE ta.id < MIN(t.id) and ta.id_device = t.id_device),0) as this_hour_consumption
FROM
  test t
WHERE 
  DATE(`date`) = '2018-10-03' and id_device = 1 --or DATE BETWEEN x AND y if this form doesn't use your index...
GROUP BY
  LPAD(HOUR(`date`), 2, '0');

重大变化是:添加了WHERE子句以将行限制为仅子集日期和设备。协调了select max()来考虑设备

还重新格式化了小时的输出格式,但这很漂亮

Edit2: 这是另一种方法,可以模拟LAG函数(升级到MySQL 8!)

SET @prev=(SELECT MAX(`total`) FROM test WHERE id_device = 1 and DATE(`date`) < '2018-10-03');
SELECT
  date_hour,
  SUM(curr_tot - prev_tot) as hour_consumption
FROM
(
  SELECT 
    LPAD(HOUR(`date`), 2, '0') as date_hour,
    @prev as prev_tot,
    @prev:=`total` as curr_tot
  FROM
    test t
  WHERE 
    DATE(`date`) = '2018-10-03' and id_device = 1 /*or DATE BETWEEN x AND y if it uses your index...*/
  ORDER BY
    `date`
) a
GROUP BY
  date_hour;
  • 我们从该设备的上一日期获得了最高总额,并将其存储到变量中

  • 我们从表中取出所需日期的记录,对它们进行排序,然后跳过它们,首先输出变量内容(对于任何给定的行都是上一行的值),然后将变量更新为该行的总数(分配后会输出)

  • 这提供了一个子查询,该查询为每一行显示当前总数和前一个总数。我们减去它们得出增量,然后每小时分组/求和。