用于显示多个日期范围之间的间隔的 SQL 查询
Posted
技术标签:
【中文标题】用于显示多个日期范围之间的间隔的 SQL 查询【英文标题】:SQL Query to show gaps between multiple date ranges 【发布时间】:2012-03-07 15:36:06 【问题描述】:我正在处理一个 s-s-rS / 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`
我的查询应该有它的结果
`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 多个事件。
更新:
好的,我会更新这个以尝试提供更多信息,因为我似乎没有很好地解释这一点(对不起!)
我正在处理的是一个包含事件详细信息的表格,每个事件都是飞行模拟器的预订,我们有许多飞行模拟(称为表中的设备),我们正在尝试生成一个s-s-rS 报告,我们可以提供给客户以显示每个 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`
虽然事件有时会重叠,但这种情况非常罕见,而且由于数据错误,一个设备上的事件与不同设备上的事件重叠并不重要,因为我需要分别了解每个设备的可用性。
【问题讨论】:
设备 ID 重要吗?或者您想查找结束日期和下一个开始日期之间的时间,而不考虑设备? 事件可以相互重叠吗?例如,根据上面的示例数据,您是否还可以让设备 3 事件 C 从 01/01/2012 09:00 - 01/01/2012 11:00 运行? 我添加了 gaps-and-islands 标签。这里有很多 G&I 问题。顺便说一句:问题定义不明确:什么是事件?您在解决方案中哪里需要它? 感谢@wildplasser 我已经更新了这个问题,希望能添加更多细节。 device=1, event='C' 向后运行?顺便说一句:请使用 ISO 日期格式。 (MDY 是邪恶的......) 【参考方案1】:查询:
假设包含区间的字段名为Start
和Finish
,表名为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。重构结果
我们现在可以重建“活动”或“非活动”区间。
inactive 区间是通过将 前一个 区间的结尾与下一个区间的开头放在一起来重构的,因此 - 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
(可以通过将来自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
给出以下结果:
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
【讨论】:
如果多个开始或结束日期具有相同的值,则使用 Distinct 将不起作用,因为 Distinct 应用于日期和行号,并且由于行号是唯一的 distinct 基本上没有任何作用。我必须将 Distinct 查询转换为 Group By。 @Andre 能否请您通过示例粘贴该组。【参考方案2】:第一个答案 - 但请参阅下面的最后一个答案,其中包含 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.DeviceId
和 e1.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
与 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
【讨论】:
【参考方案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
)
【讨论】:
但这不涉及重叠事件。请参阅上面的提问者,询问时间为 9:00 - 11:00。在这种情况下,这将失败并给出两行——一行从 10:00-18:00 和第二行从 11:00-18:00。例如,请参阅:sqlfiddle.com/#!3/6e49f/1 谢谢,这看起来是我查询的一个很好的起点。【参考方案4】:这是否解决了您的问题:
http://www.simple-talk.com/sql/t-sql-programming/find-missing-date-ranges-in-sql/ http://www.simple-talk.com/sql/t-sql-programming/missing-date-ranges--the-sequel/第二个似乎更相关
'有一个表,其中两列是 DateFrom 和 DateTo。 两列都包含日期和时间值。如何找到 缺少日期范围,或者换句话说,所有日期范围 未包含在表中的任何条目中'。
【讨论】:
【参考方案5】:这是我刚刚做的一个 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 特有的功能,但在其他引擎中可能存在类似的东西。
此查询将为您提供所有已填写的分钟数和所有空白的分钟数。您可以将此查询包装在外部查询中,该查询可以按小时、天分组或执行一些窗口函数操作以获得您需要的准确输出。为了我的目的,我只需要计算是否有空格。
【讨论】:
以上是关于用于显示多个日期范围之间的间隔的 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章