SQL-запрос, чтобы показать разрывы между несколькими диапазонами дат - PullRequest
15 голосов
/ 07 марта 2012

Я работаю над проектом SSRS / SQL и пытаюсь написать запрос, чтобы получить промежутки между датами, и я совершенно не понимаю, как написать это. В основном у нас есть несколько устройств, которые можно запланировать для использования, и мне нужноотчет, чтобы показать, когда они не используются.

У меня есть таблица с идентификаторами устройств, 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, который мы можем предоставить клиенту, чтобы показать дни / время, когда каждый сим доступен.

Итак, я собираюсь передать параметр даты начала / окончания и выбрать все доступные значения между этими датами.Результаты должны отображаться примерно так:

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 ]

20 голосов
/ 07 марта 2012

Запрос:

Если предположить, что поля, содержащие интервал, имеют имена 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.Реконструкция результата

Теперь мы можем реконструировать либо «активный», либо «неактивный» интервалы.

неактивные интервалы восстанавливаются путем соединения конца предыдущий интервал с началом следующего, следовательно - 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
4 голосов
/ 07 марта 2012

Первый ответ - но смотрите окончательный вариант ниже с дополнительными ограничениями, добавленными OP.

- Если вы хотите получить следующий startTime после самого последнего endTime и избежать перекрытий, вам нужно что-то вроде:

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 Fiddle здесь: http://sqlfiddle.com/#!3/3899c/8

-

OK, окончательное редактирование,Вот запрос на добавление 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

3 голосов
/ 07 марта 2012

Я собираюсь предположить, что это не так просто ... но вот запрос, основанный на моем текущем понимании вашего сценария:

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
        )
2 голосов
/ 07 марта 2012

Решает ли это вашу проблему:

Второй кажется более актуальным

'Существует таблица, в которой два столбца - DateFrom и DateTo. Оба столбца содержат значения даты и времени. Как найти отсутствующие диапазоны дат или, другими словами, все диапазоны дат, которые не охвачено ни одной из записей в таблице '.

1 голос
/ 04 января 2018

Вот решение 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, но, вероятно, что-то похожее существует в других движках.

Этот запрос даст вам все заполненные минуты и все пустые. Вы можете заключить этот запрос во внешний запрос, который можно сгруппировать по часам, дням или выполнить некоторые операции с оконными функциями, чтобы получить точный результат по мере необходимости. Для моих целей мне нужно было только сосчитать, есть ли пробелы или нет.

...