【问题标题】:SQL Query to get last 24 contiguous hours with potentially missing hoursSQL 查询以获取最近 24 小时的连续小时数,可能会丢失小时数
【发布时间】:2020-10-07 19:07:47
【问题描述】:

所以我需要从查询中返回最近的 24 个连续小时。表保存每小时数据。获取最后 24 小时不是问题,但我有时会丢失数据,因此需要更早地找到第一个“完整”的 24 小时集。

select date, value from TABLE
where date >= (select max(date)-1 from TABLE) 

但是,有时我会错过这个查询的时间。我怎样才能确保我总是得到 24 行并且它是最近的 24 小时块?

以下示例:

A 类通知,缺少 2020 年 1 月 31 日第 23 小时,因此应返回 2020 年 1 月 31 日 22 至 2020 年 1 月 30 日 23 小时。B 类应返回 2020 年 2 月 1 日0 小时至 2020 年 1 月 31 日 1 小时。

【问题讨论】:

  • 请描述您所说的“全套 24 小时”是什么意思。样本数据和期望的结果会有所帮助。

标签: sql oracle select sql-order-by window-functions


【解决方案1】:

您需要几个步骤。首先,对于每条记录,您需要查看它有多少小时的连续先前数据。这就是grouped_hour_data 子句在下面的解决方案中所做的。

然后,您需要从该结果中进行选择,仅获取具有完整 24 小时连续先前数据的行。然后只获取前 24 行。

此解决方案经过简化,以利用您的所有日期都被截断为小时并且没有重复的事实。如果您的问题比这更复杂,这个解决方案仍然可以支持它,但需要对其进行修改。

在此示例中,我们创建了几天前的测试数据,但从 16 日和 17 日的各个小时删除数据,以便第一个连续 24 小时时段在 16 日提前结束。

alter session set nls_date_format = 'DD-MON-YYYY HH24:MI:SS';

with hour_data_raw AS (
SELECT to_date('17-JUN-2020 17:00:00','DD-MON-YYYY HH24:MI:SS') - ( INTERVAL '1' HOUR ) * rownum dte
FROM dual
CONNECT BY rownum <= 200 ),
hour_data AS ( SELECT dte 
               FROM hour_data_raw
               WHERE NOT TRUNC(dte,'HH') = to_date('17-JUN-2020 02:00:00','DD-MON-YYYY HH24:MI:SS')
               AND NOT TRUNC(dte,'HH') = to_date('16-JUN-2020 02:00:00','DD-MON-YYYY HH24:MI:SS') ),
-- SOLUTION BEGINS HERE... everything above is just test data
-- WITH...
   grouped_hour_data AS (
     SELECT h.*, count(trunc(h.dte,'HH')) OVER ( ORDER BY dte desc RANGE BETWEEN CURRENT ROW AND INTERVAL '1' DAY - INTERVAL '1' SECOND FOLLOWING ) cnt
     FROM hour_data h
     ORDER BY dte)
SELECT * FROM grouped_hour_data
WHERE cnt = 24
ORDER BY dte desc
FETCH FIRST 24 ROWS ONLY;
+----------------------+-----+
|         DTE          | CNT |
+----------------------+-----+
| 16-JUN-2020 01:00:00 |  24 |
| 16-JUN-2020 00:00:00 |  24 |
| 15-JUN-2020 23:00:00 |  24 |
| 15-JUN-2020 22:00:00 |  24 |
| 15-JUN-2020 21:00:00 |  24 |
| 15-JUN-2020 20:00:00 |  24 |
| 15-JUN-2020 19:00:00 |  24 |
| 15-JUN-2020 18:00:00 |  24 |
| 15-JUN-2020 17:00:00 |  24 |
| 15-JUN-2020 16:00:00 |  24 |
| 15-JUN-2020 15:00:00 |  24 |
| 15-JUN-2020 14:00:00 |  24 |
| 15-JUN-2020 13:00:00 |  24 |
| 15-JUN-2020 12:00:00 |  24 |
| 15-JUN-2020 11:00:00 |  24 |
| 15-JUN-2020 10:00:00 |  24 |
| 15-JUN-2020 09:00:00 |  24 |
| 15-JUN-2020 08:00:00 |  24 |
| 15-JUN-2020 07:00:00 |  24 |
| 15-JUN-2020 06:00:00 |  24 |
| 15-JUN-2020 05:00:00 |  24 |
| 15-JUN-2020 04:00:00 |  24 |
| 15-JUN-2020 03:00:00 |  24 |
| 15-JUN-2020 02:00:00 |  24 |
+----------------------+-----+

编辑:处理类别字段

要处理您添加的额外 category 字段,您需要做一些事情。

首先,PARTITION BY category 在计算 cnt 字段时。这将导致在计算此值时分别处理每个类别的数据。因此,例如,A 类在第 2 小时的值不会算作 B 类在第 2 小时的值。

其次,你不能再使用FETCH FIRST 24 ROWS ONLY来获取你想要的数据,因为你现在需要每个类别的前24行。因此,您需要一个额外的步骤(ordered_groups,在下面的修改后的查询中)来对每个类别中具有连续 24 小时数据的行进行排序。调用该排序rn,然后在最终查询中选择where rn &lt;= 24

WITH grouped_hour_data AS (
     SELECT h.*, count(trunc(h.dte,'HH')) OVER ( 
              PARTITION BY category 
              ORDER BY dte desc 
              RANGE BETWEEN CURRENT ROW 
                  AND INTERVAL '1' DAY - INTERVAL '1' SECOND FOLLOWING ) cnt
     FROM hour_data h
     ORDER BY dte),
 ordered_groups AS (
   SELECT ghd.*, row_number() over ( partition by ghd.category order by ghd.dte desc ) rn
   FROM   grouped_hour_data
   WHERE  ghd.cnt = 24 )
SELECT * FROM ordered_groups
WHERE rn <= 24;
ORDER BY category, dte desc;

披露:我没有测试这个更新的逻辑,所以可能会有一些错误。

【讨论】:

  • 我接受了这个答案,因为它适用于我提供的示例。但是,如果我添加一层复杂性,则此解决方案可能不起作用。 grouped_hour_data AS (SELECT h.*,count(trunc(h.dte,'HH') OVER (ORDER BY h.dte desc RANGE BETWEEN BETWEEN AND INTERVAL '1' DAY - INTERVAL '1' SECOND FOLLOWING ) cnt FROM ts_avg_data h group by h.* ORDER BY h.resource_name,h.dte) 如果我在计数部分添加 group by,我会收到错误消息,不允许使用窗口函数。有什么方法可以修改您的解决方案来解决这个问题?
  • 你想按什么分组?请使用适当的测试数据更新您的问题,或提出新问题。
  • 更新了问题,以说明其他分组依据
  • 太棒了!必须在“ordered_groups”分区中添加这个:order by ghd.dte desc。经过几次抽查后,这似乎起作用了,而且比我预期的要快。再次感谢!
【解决方案2】:

看起来您实际上是小时表中的最后 24 行。如果是这样,您可以使用行限制子句:

select date, value
from mytable
order by date desc
fetch first 24 rows only

或者如果每小时可能有多条记录,那么另一个选项是dense_rank()

select date, value
from (
    select t.*, dense_rank() over(order by trunc(date, 'hh24') desc) rn
    from mytable t
) t
where rn <= 24

【讨论】:

    【解决方案3】:

    [编辑] 以下内容应该适合您:

    IF OBJECT_ID('tempdb..#hours') IS NOT NULL 
        DROP TABLE #hours
    
    create table #hours ([Hour] int)
    
    insert into #hours select 1
    insert into #hours select 2
    insert into #hours select 3
    insert into #hours select 4
    insert into #hours select 5
    insert into #hours select 6
    insert into #hours select 7
    insert into #hours select 8
    insert into #hours select 9
    insert into #hours select 10
    insert into #hours select 11
    insert into #hours select 12
    insert into #hours select 13
    insert into #hours select 14
    insert into #hours select 15
    insert into #hours select 16
    insert into #hours select 17
    insert into #hours select 18
    insert into #hours select 19
    insert into #hours select 20
    insert into #hours select 21
    insert into #hours select 22
    insert into #hours select 23
    insert into #hours select 24
    
    -- step 1 --
    IF OBJECT_ID('tempdb..#temp1') IS NOT NULL 
        DROP TABLE #temp1
    
    select
        t.[Date]
        ,convert(date,t.[Date]) [Day]
        ,datepart(hour,t.[Date]) [Hour]
        ,t.Value
    
    into
        #temp1
    
    from
        #yourtable t
    
    -- step 2 --
    IF OBJECT_ID('tempdb..#temp2') IS NOT NULL 
        DROP TABLE #temp2
    
    select
        max(t.[Day]) [MostRecentDay]
    
    into
        #temp2
    
    from
        #temp1 t
        cross apply (
            select
                count(distinct i.[Hour]) [HrCt]
            from
                #temp1 i
            where
                t.[Day] = i.[Day]
        ) hc
    
    where
        hc.HrCt <> 24
    
    -- step 3 --
    IF OBJECT_ID('tempdb..#temp3') IS NOT NULL 
        DROP TABLE #temp3
    
    select
        min(t1.[Hour]) [FirstBlank]
    
    into
        #temp3
    
    from
        #temp2 t2
        inner join #temp1 t1
            on t2.[MostRecentDay] = t1.[Day]
        left outer join #hours h
            on t1.[Hour] = h.[Hour]
    
    where
        h.[Hour] is null
    
    -- final select --
    select top 24
        t1.[Date]
        ,t1.[Value]
    
    from
        #temp1 t1
        cross join #temp2 t2
        cross join #temp3 t3
    
    where
        t1.[Date] < convert(datetime,concat(t2.[MostRecentDay],' ',t3.[FirstBlank]))
    
    order by
        t1.[Date] desc
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-03
      • 1970-01-01
      • 2021-01-28
      • 1970-01-01
      • 1970-01-01
      • 2021-03-29
      • 1970-01-01
      相关资源
      最近更新 更多