Проверка перекрытия временного диапазона, проблема сторожа [SQL] - PullRequest
8 голосов
/ 23 апреля 2009

Я сталкиваюсь с препятствием на пути к более серьезной проблеме.

В рамках большого запроса мне нужно решить проблему «ночного сторожа». У меня есть таблица с изменениями графика как таковыми:

ID | Start          | End
1  | 2009-1-1 06:00 | 2009-1-1 14:00
2  | 2009-1-1 10:00 | 2009-1-1 18:00
3  | 2009-2-1 20:00 | 2009-2-2 04:00
4  | 2009-2-2 06:00 | 2009-2-2 14:00

Как часть запроса, мне нужно определить, есть ли хотя бы один сторож в комнате постоянно в течение заданного промежутка времени.

Таким образом, если я указал диапазон от 2009-1-1 06:00 до 2009-1-1 12:00, результат будет верным, потому что сдвиги 1 и 2 объединяются, чтобы покрыть этот период времени - фактически любое число смен может быть объединено в цепочку, чтобы поддерживать наблюдение. Однако, если я проверил 2009-2-1 22:00 до 2009-1-2 10:00, результат будет ложным, потому что между 4 и 6 утра следующего дня будет перерыв.

Я хотел бы реализовать это либо в LINQ, либо как пользовательскую функцию в SQL Server (2005), поскольку в обоих случаях это лишь часть логики более крупного запроса, который должен бегать, чтобы определить элементы, которые требуют внимания. Реальный набор данных включает в себя около ста сменных записей, пересекающих любой заданный период времени, но не всегда охватывающих весь диапазон.

Самое близкое, что я нашел, это Как сгруппировать ранжированные значения с помощью SQL Server для диапазонов номеров, однако это зависит от каждого диапазона, заканчивающегося непосредственно перед началом следующего диапазона. Если бы я мог создать такой же единый вид часов, принимая во внимание только перекрывающиеся часы, то было бы тривиально проверить, было ли охвачено определенное время. Единый вид будет выглядеть так:

Start          | End
2009-1-1 06:00 | 2009-1-1 18:00
2009-2-1 20:00 | 2009-2-2 04:00
2009-2-2 06:00 | 2009-2-2 14:00

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

Ответы [ 4 ]

2 голосов
/ 23 апреля 2009

Вот способ сгладить диапазон дат как этот

Start          | End
2009-1-1 06:00 | 2009-1-1 18:00
2009-2-1 20:00 | 2009-2-2 04:00
2009-2-2 06:00 | 2009-2-2 14:00

Вы должны сравнить предыдущие и следующие даты в каждой строке и посмотреть, является ли

  • Текущая строка Начало дата попадает в диапазон дат предыдущей строки.
  • Текущая строка Конец дата попадает в диапазон дат следующей строки.

alt text

Используя приведенный выше код, реализовать UDF очень просто:

create function fnThereIsWatchmenBetween(@from datetime, @to datetime)
returns bit
as
begin
    declare @_Result bit

    declare @FlattenedDateRange table (
        Start   datetime,
        [End]   datetime
    )

    insert  @FlattenedDateRange(Start, [End])
    select  distinct 
            Start = 
                case 
                    when Pv.Start is null then Curr.Start 
                    when Curr.Start between Pv.Start and Pv.[End] then Pv.Start
                    else Curr.Start 
                end,
            [End] = 
                case 
                    when Curr.[End] between Nx.Start and Nx.[End] then Nx.[End] 
                    else Curr.[End] 
                end
    from    shift Curr
            left join shift Pv on Pv.ID = Curr.ID - 1 --; prev
            left join shift Nx on Nx.ID = Curr.ID + 1 --; next

    if exists(  select  1
                from    FlattenedDateRange R
                where   @from between R.Start and R.[End]
                        and @to between R.Start and R.[End]) begin
        set @_Result = 1    --; There is/are watchman/men during specified date range
    end
    else begin
        set @_Result = 0    --; There is NO watchman
    end

    return @_Result
end
1 голос
/ 23 апреля 2009

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

select 1 
from shifts s1 where not exists
    (select 1 from shifts s2
     where s2.start<=s1.end and s2.end > s1.end
    )
    and s1.end>=start_of_range and s1.end<  end_of_range
union
select 1 
where not exists
    (select 1 from shifts s2 
      where s2.start<=start_of_range and s2.end > start_of_range
    )

Если это не пусто, то у вас есть неохраняемый интервал. Я подозреваю, что он будет работать в квадратичном времени, поэтому он может быть медленнее, чем "sort, fetch and loop".

0 голосов
/ 03 марта 2015

Я смотрел на диапазоны дат и думал, что снова зайду на этот вопрос. Я могу потерять сознание, но этих двух условий будет достаточно

(1) Shift is not at beginning of range and has no left neighbour

OR

(2) Shift is not at end of range and has no right neighbour.

Оцените, что это может быть не самым эффективным.

CREATE TABLE times
(
TimeID int,
StartTime Time,
EndTime Time
)

INSERT INTO times
VALUES
(1,'10:00:00','11:00:00'),
(2,'11:00:00','12:00:00'),
(3,'13:00:00','14:00:00'),
(4,'14:30:00','15:00:00'),
(5,'15:00:00','16:00:00'),
(6,'16:00:00','17:00:00')

declare @start_of_range time ='09:30:00'
declare @end_of_range time = '17:30:00'



select timeID,StartTime,EndTime 
from times s1 where
-- No left neighbour and not at beginning of range
   not exists
    (select 1 from times s2
     where s2.startTime < s1.startTime and s2.endTime >= s1.startTime
    )
    and s1.StartTime>@start_of_range
  or
-- No right neighbour and not at end of range
   not exists
    (select 1 from times s2
     where s2.startTime <= s1.endTime and s2.endTime > s1.endTime
    )
    and s1.EndTime<@end_of_range

Результирующий набор

timeID  StartTime   EndTime
1   10:00:00.0000000    11:00:00.0000000
2   11:00:00.0000000    12:00:00.0000000
3   13:00:00.0000000    14:00:00.0000000
4   14:30:00.0000000    15:00:00.0000000
6   16:00:00.0000000    17:00:00.0000000

На самом деле необходимо проверять только правых или левых соседей, если вы проверяете начало и конец диапазона, чтобы вы могли ввести начало диапазона как фиктивный интервал и просто проверить Право соседей выглядит следующим образом: -

select * from
(
select timeID,StartTime,EndTime 
from times union select 0,@start_of_range,@start_of_range) s1
where
    not exists
    (select 1 from times s2
     where s2.startTime<=s1.endTime and s2.endTime > s1.endTime
    )
    and s1.EndTime<@end_of_range

Результирующий набор

timeID  StartTime   EndTime
0   09:30:00.0000000    09:30:00.0000000
2   11:00:00.0000000    12:00:00.0000000
3   13:00:00.0000000    14:00:00.0000000
6   16:00:00.0000000    17:00:00.0000000
0 голосов
/ 24 апреля 2009

Один из способов - создать временную таблицу со строкой для каждого значения времени, которое необходимо проверить (что является функцией разрешения ваших смен).

Если бы это были минуты, то было бы 60 * 24 = 1440 строк в день; около 10 тысяч строк в неделю.

Тогда SQL относительно прост:

ВЫБРАТЬ СЧЕТ (1)
ОТ # минут м
СЛЕДУЮЩАЯ СОЕДИНЕНИЕ переключает на ВКЛ. СЧЕТЧИК (1) = 0

Преимущество заключается также в возможности показать, сколько смен покрывает одно и то же время.

Время выполнения должно быть незначительным с учетом масштабов, которые вы описали.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...