SQL Query显示多个日期范围之间的差距

时间:2012-03-07 15:36:06

标签: sql stored-procedures gaps-and-islands

我正在研究一个SSRS / SQL项目并试图编写一个查询以获得日期之间的差距,我完全迷失了如何写这个。基本上我们有许多设备可以安排使用,我需要一份报告,显示它们何时不使用。

我有一个包含Device ID,EventStart和EventEnd时间的表,我需要运行一个查询来获取每个设备的这些事件之间的时间,但我不确定如何做到这一点。

例如:

Device 1 Event A runs from `01/01/2012 08:00 - 01/01/2012 10:00`
Device 1 Event B runs from `01/01/2012 18:00 - 01/01/2012 20:00`    
Device 1 Event C runs from `02/01/2012 18:00 - 02/01/2012 20:00`    
Device 2 Event A runs from `01/01/2012 08:00 - 01/01/2012 10:00`
Device 2 Event B runs from `01/01/2012 18:00 - 01/01/2012 20:00`

我的查询应该有结果

`Device 1 01/01/2012 10:00 - 01/01/2012 18:00`
`Device 1 01/01/2012 20:00 - 02/01/2012 18:00`
`Device 2 01/01/2012 10:00 - 01/01/2012 18:00`

此表中平均有大约4到5台设备,可能有200 - 300 +个事件。

更新

好的我会更新这个以尝试提供更多信息,因为我似乎没有解释得太好了(抱歉!)

我正在处理的是一张包含事件详情的表格,每个事件都是飞行模拟器的预订,我们有许多飞行模拟器(在表格中称为设备),我们正在尝试生成一个我们可以向客户提供SSRS报告,以显示每个SIM卡可用的日期/时间。

因此,我将传入一个开始/结束日期参数,并选择这些日期之间的所有可用性。结果应显示为:

Device   Available_From       Available_To
 1       01/01/2012 10:00    01/01/2012 18:00`
 1       01/01/2012 20:00    02/01/2012 18:00`
 2       01/01/2012 10:00    01/01/2012 18:00`

此外,事件有时可能会重叠,但这种情况非常罕见,并且由于数据不良,因此我需要分别知道每台设备的可用性,因此一台设备上的事件与其他设备上的事件重叠并不重要。

5 个答案:

答案 0 :(得分:20)

查询:

假设包含间隔的字段名为StartFinish,并且表名为YOUR_TABLE,则查询...

SELECT Finish, Start
FROM
    (
        SELECT DISTINCT Start, ROW_NUMBER() OVER (ORDER BY Start) RN
        FROM YOUR_TABLE T1
        WHERE
            NOT EXISTS (
                SELECT *
                FROM YOUR_TABLE T2
                WHERE T1.Start > T2.Start AND T1.Start < T2.Finish
            )
        ) T1
    JOIN (
        SELECT DISTINCT Finish, ROW_NUMBER() OVER (ORDER BY Finish) RN
        FROM YOUR_TABLE T1
        WHERE
            NOT EXISTS (
                SELECT *
                FROM YOUR_TABLE T2
                WHERE T1.Finish > T2.Start AND T1.Finish < T2.Finish
            )
    ) T2
    ON T1.RN - 1 = T2.RN
WHERE
    Finish < Start

...对您的测试数据给出以下结果:

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000

此查询的重要属性是它也适用于重叠间隔。


算法:

1。合并重叠间隔

子查询T1仅接受超出其他区间的区间开始。子查询T2对于间隔结束执行相同的操作。这就是删除重叠的原因。

如果两个相同的间隔开始(或结束)两个超出其他间隔,则DISTINCT很重要。 WHERE Finish < Start只是消除了任何空间隔(即持续时间为0)。

我们还附加了一个相对于时序的行号,这将在下一步中使用。

T1收益:

Start                       RN
2012-01-01 08:00:00.000     1
2012-01-01 18:00:00.000     2

T2收益:

Finish                      RN
2012-01-01 10:00:00.000     1
2012-01-01 20:00:00.000     2

2。重建结果

我们现在可以重建“活动”或“非活动”间隔。

非活动区间是通过将上一个区间的结尾与下一个区间的开头放在一起来重建的,因此- 1位于ON中}子句。实际上,我们把...

Finish                      RN
2012-01-01 10:00:00.000     1

...和...

Start                       RN
2012-01-01 18:00:00.000     2

......一起,导致:

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000

(可以通过使用T1T2的行与JOIN ... ON T1.RN = T2.RN的行放在一起,然后使用WHERE并还原Device Event Start Finish Device 1 Event A 2012-01-01 08:00:00.000 2012-01-01 10:00:00.000 Device 2 Event B 2012-01-01 18:00:00.000 2012-01-01 20:00:00.000 Device 3 Event C 2012-01-02 11:00:00.000 2012-01-02 15:00:00.000 Device 4 Event D 2012-01-02 10:00:00.000 2012-01-02 12:00:00.000 Device 5 Event E 2012-01-02 10:00:00.000 2012-01-02 15:00:00.000 Device 6 Event F 2012-01-03 09:00:00.000 2012-01-03 10:00:00.000 来重建活动区间。)


示例:

这是一个稍微更现实的例子。以下测试数据:

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000
2012-01-01 20:00:00.000     2012-01-02 10:00:00.000
2012-01-02 15:00:00.000     2012-01-03 09:00:00.000

给出以下结果:

{{1}}

答案 1 :(得分:4)

第一个答案 - 但请参阅下面的最后一个答案,其中附加的限制由OP添加。

- 如果你想在最近的endTime之后获得下一个startTime并避免重叠,你需要类似的东西:

select
    distinct
    e1.deviceId,
    e1.EventEnd,
    e3.EventStart
from Events e1 
join Events e3 on e1.eventEnd < e3.eventStart     /* Finds the next start Time */
and e3.eventStart = (select min(eventStart) from Events e5
                     where e5.eventStart > e1.eventEnd)
and not exists (select *                          /* Eliminates an e1 rows if it is overlapped */
                from Events e5 
                where e5.eventStart < e1.eventEnd
                    and e5.eventEnd > e1.eventEnd)

对于你的三行:

INSERT INTO Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')
insert into Events values (2, '01/01/2012 09:00', '01/01/2012 11:00')

这给出了1个结果:

January, 01 2012 11:00:00-0800  January, 01 2012 18:00:00-0800

但是,我假设您可能也希望在DeviceId上匹配。在这种情况下,在联接上,您需要添加e1.DeviceId = e3.DeviceIde1.deviceId = e5.deviceId

SQL小提琴:http://sqlfiddle.com/#!3/3899c/8

-

好的,最后编辑。这是一个在deviceIds中添加的查询,并添加一个独特的帐户来同时结束事件:

SELECT distinct
    e1.DeviceID,
    e1.EventEnd as LastEndTime,
    e3.EventStart as NextStartTime
FROM Events e1 
join Events e3 on e1.eventEnd < e3.eventStart
     and e3.deviceId = e1.deviceId
     and e3.eventStart = (select min(eventStart) from Events e5
                     where e5.eventStart > e1.eventEnd
                    and e5.deviceId = e3.deviceId)
where not exists (select * from Events e7 
                    where e7.eventStart < e1.eventEnd
                      and e7.eventEnd > e1.eventEnd
                      and e7.deviceId = e1.deviceId)
order by e1.deviceId, e1.eventEnd

到e3的连接找到下一个开始。加入e5可确保这是当前结束时间之后的最早开始时间。如果考虑的行的结束时间与另一行重叠,则连接到e7会消除一行。

对于这些数据:

INSERT INTO Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')
insert into Events values (2, '01/01/2012 09:00', '01/01/2012 11:00')
insert into Events values (2, '01/02/2012 11:00', '01/02/2012 15:00')
insert into Events values (1, '01/02/2012 10:00', '01/02/2012 12:00')
insert into Events values (2, '01/02/2012 10:00', '01/02/2012 15:00')
insert into Events values (2, '01/03/2012 09:00', '01/03/2012 10:00')

你得到这个结果:

1   January, 01 2012 10:00:00-0800  January, 02 2012 10:00:00-0800
2   January, 01 2012 11:00:00-0800  January, 01 2012 18:00:00-0800
2   January, 01 2012 20:00:00-0800  January, 02 2012 10:00:00-0800
2   January, 02 2012 15:00:00-0800  January, 03 2012 09:00:00-0800

SQL小提琴:http://sqlfiddle.com/#!3/db0fa/3

答案 2 :(得分:3)

我会假设这不是真的很简单......但这是基于我目前对你的场景的理解的查询:

DECLARE @Events TABLE (
    DeviceID INT,
    EventStart DATETIME,
    EventEnd DATETIME
)

INSERT INTO @Events VALUES (1, '01/01/2012 08:00', '01/01/2012 10:00')
INSERT INTO @Events VALUES (2, '01/01/2012 18:00', '01/01/2012 20:00')

SELECT
    e1.DeviceID,
    e1.EventEnd,
    e2.EventStart
FROM 
    @Events e1 
    JOIN @Events e2 
        ON e2.EventStart = (
            SELECT MIN(EventStart)
            FROM @Events
            WHERE EventStart > e1.EventEnd
        )

答案 3 :(得分:2)

这是否可以解决您的问题:

第二个似乎更相关

  

'有一个表,其中两列是DateFrom和DateTo。   两列都包含日期和时间值。如何找到   缺少日期范围,换句话说,是所有日期范围   表格'。

中的任何条目都没有涵盖

答案 4 :(得分:1)

这是我刚刚做过的Postgres解决方案,它不涉及存储过程:

SELECT minute, sum(case when dp.id is null then 0 else 1 end) as s 
FROM generate_series( 
   '2017-12-28'::timestamp,
   '2017-12-30'::timestamp,
   '1 minute'::interval
) minute 
left outer join device_periods as dp
on minute >= dp.start_date and minute < dp.end_date 
group by minute order by minute

generate_series函数生​​成一个表,该表在日期范围内每分钟有一行。您可以将间隔更改为1秒,更准确。这是一个特定于postgres的函数,但在其他引擎中可能存在类似的东西。

此查询将为您提供所有已填写的分钟,以及所有空白分钟。您可以将此查询包装在外部查询中,该查询可以按小时,天进行分组,或者执行一些窗口函数操作以根据需要获得准确的输出。就我的目的而言,我只需要计算是否有空白。