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

Pur*_*ish 15 sql stored-procedures gaps-and-islands

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

我有一个设备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`
Run Code Online (Sandbox Code Playgroud)

我的查询应该有结果

`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`
Run Code Online (Sandbox Code Playgroud)

此表中平均有大约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`
Run Code Online (Sandbox Code Playgroud)

此外,事件有时可能会重叠,但这种情况非常罕见,并且由于数据不良,一个设备上的事件与另一个设备上的事件重叠并不重要,因为我需要单独了解每个设备的可用性.

Bra*_*vic 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
Run Code Online (Sandbox Code Playgroud)

...在测试数据上给出以下结果:

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000
Run Code Online (Sandbox Code Playgroud)

此查询的重要属性是它也可以在重叠间隔上工作.


算法:

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
Run Code Online (Sandbox Code Playgroud)

T2收益率:

Finish                      RN
2012-01-01 10:00:00.000     1
2012-01-01 20:00:00.000     2
Run Code Online (Sandbox Code Playgroud)

2.重建结果

我们现在可以重建"活动"或"非活动"间隔.

所述非活动间隔由放在一起的的端部重建先前间隔与下一个的开始,因此,- 1ON子句.实际上,我们把......

Finish                      RN
2012-01-01 10:00:00.000     1
Run Code Online (Sandbox Code Playgroud)

...和...

Start                       RN
2012-01-01 18:00:00.000     2
Run Code Online (Sandbox Code Playgroud)

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

Finish                      Start
2012-01-01 10:00:00.000     2012-01-01 18:00:00.000
Run Code Online (Sandbox Code Playgroud)

(可以通过从T1行旁边放置行T2,通过使用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
Run Code Online (Sandbox Code Playgroud)

给出以下结果:

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
Run Code Online (Sandbox Code Playgroud)

  • 如果多个"开始"或"完成日期"具有相同的值,则使用"区别"将不起作用,因为"区别"将应用于日期和行号,并且由于行号唯一不同,因此基本上不执行任何操作.我不得不将Distinct查询转换为Group By. (2认同)

Mik*_*yan 5

第一个答案 - 但请参阅下面的最后一个答案,其中 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)
Run Code Online (Sandbox Code Playgroud)

对于三行的情况:

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')
Run Code Online (Sandbox Code Playgroud)

这给出了 1 个结果:

January, 01 2012 11:00:00-0800  January, 01 2012 18:00:00-0800
Run Code Online (Sandbox Code Playgroud)

但是,我假设您可能还想在 DeviceId 上进行匹配。在这种情况下,在连接上,您将添加e1.DeviceId = e3.DeviceIde1.deviceId = e5.deviceId

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

——

好的,最终编辑。这是一个添加 deviceIds 并添加一个 distinct 以说明同时结束事件的查询:

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
Run Code Online (Sandbox Code Playgroud)

连接到 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')
Run Code Online (Sandbox Code Playgroud)

你得到这个结果:

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
Run Code Online (Sandbox Code Playgroud)

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