SQL Server:Gap / Island,datetime,连续块365天块

时间:2014-02-27 10:24:44

标签: sql sql-server gaps-and-islands

我有一张如下表: -

tblMeterReadings

  

id meter period_start period_end amount
      1 1 2014-01-01 00:00 2014-01-01 00:29:59 100.3
      2 1 2014-01-01 00:30 2014-01-01 00:59:59 50.5
      3 1 2014-01-01 01:00 2014-01-01 01:29:59 70.7
      4 1 2014-01-01 01:30 2014-01-01 01:59:59 900.1
      5 1 2014-01-01 02:00 2014-01-01 02:29:59 400.0
      6 1 2014-01-01 02:30 2014-01-01 02:59:59 200.3
      7 1 2014-01-01 03:00 2014-01-01 03:29:59 100.8
      8 1 2014-01-01 03:30 2014-01-01 03:59:59 140.3

这是来自'2014-01-01 00:00' to '2014-01-01 3:59:59'的一个微小的“连续区块”。

在真实表格中,存在多年的“连续块”。

我需要找到最近连续365完整日的period_start和period_end(按米列为文件)。

当我说 COMPLETE DAY 时,我的意思是一天的条目跨越00:00 to 23:59。 当我说连续时,我的意思是肯定不会缺少任何日子。

我想选择构成连续完整日块的所有行。

我还需要输出:

  

block_start block_end total_amount_for_block
      2013-02-26 00:00 2014-02-26 23:59:59 1034234.5

这超出了我的范围,所以如果有人可以解决......我会非常感动。

2 个答案:

答案 0 :(得分:1)

由于您的粒度为1秒,因此您需要将时段扩展为开始和结束之间的所有日期/时间,间隔为1秒。要做到这一点,你需要与数字表交叉连接(数字表是通过从任意系统视图排列对象ID动态生成的,我将其限制为TOP 86400,因为这是一天中的秒数,并且你说你的时间段从不超过一天):

WITH Numbers AS
(   SELECT  TOP (86400) 
            Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
    FROM    sys.all_objects a
            CROSS JOIN sys.all_objects b
    ORDER BY a.object_id
)
SELECT  r.ID, r.meter, dt.[DateTime]
FROM    tblMeterReadings r
        CROSS JOIN Numbers n
        OUTER APPLY 
        (   SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
        ) dt
WHERE   dt.[DateTime] <= r.Period_End;

然后,您可以在连续范围内执行正常间隙和岛屿分组:

WITH Numbers AS
(   SELECT  TOP (86400) 
            Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
    FROM    sys.all_objects a
            CROSS JOIN sys.all_objects b
    ORDER BY a.object_id
), Grouped AS
(   SELECT  r.meter, 
            Amount = CASE WHEN Number = 1 THEN r.Amount ELSE 0 END,
            dt.[DateTime],
            GroupingSet = DATEADD(SECOND, 
                                    -DENSE_RANK() OVER(PARTITION BY r.Meter 
                                                        ORDER BY dt.[DateTime]), 
                                    dt.[DateTime])
    FROM    tblMeterReadings r
            CROSS JOIN Numbers n
            OUTER APPLY 
            (   SELECT [DateTime] = DATEADD(SECOND, n.Number, r.period_start)
            ) dt
    WHERE   dt.[DateTime] <= r.Period_End
)
SELECT  meter, 
        PeriodStart = MIN([DateTime]), 
        PeriodEnd = MAX([DateTime]), 
        Amount = SUM(Amount)
FROM    Grouped
GROUP BY meter, GroupingSet
HAVING DATEADD(YEAR, 1, MIN([DateTime])) < MAX([DateTime]);

N.B。由于加入Number会导致重复数量,因此必须使用 CASE WHEN Number = 1 THEN r.Amount ELSE 0 END 将所有重复项设置为0,即仅包含每个ID的第一行的金额

删除样本数据的Having子句将给出:

meter | PeriodStart         | PeriodEnd           | Amount
------+---------------------+---------------------+----------
1     | 2014-01-01 00:00:00 | 2014-01-01 03:59:59 |  1963

<强> Example on SQL Fiddle

答案 1 :(得分:0)

你可以试试这个:

Select MIN(period_start) as "block start"
     , MAX(period_end) as "block end"
     , SUM(amount) as "total amount"
FROM YourTable
GROUP BY    datepart(year, period_start)
        ,   datepart(month, period_start)
        ,   datepart(day, period_start)
        ,   datepart(year, period_end)
        ,   datepart(month, period_end)
        ,   datepart(day, period_end)
Having      datepart(year, period_start) = datepart(year, period_end)
        AND datepart(month, period_start) = datepart(month, period_end)
        AND datepart(day, period_start) = datepart(day, period_end)
        AND datepart(hour,  MIN(period_start)) = 0 
        AND datepart(minute,MIN(period_start)) = 0
        AND datepart(hour,  MAX(period_end)) = 23
        AND datepart(minute,MIN(period_end)) = 59