Как избавиться от CURSOR, если для каждой FETCH требуется SELECT на основе предыдущего результата

У нас есть пара таблиц, назовем их «Sections» и «SectionTimeWindows». Таблица разделов содержит данные об участках «движения», которое начинается в каком-то месте, заканчивается в каком-то месте и имеет тип. Тип важен, поскольку он определяет, сколько времени займет раздел — каждый тип раздела имеет набор временных окон, и окно определяет время работы раздела в зависимости от того, когда мы доберемся до этого раздела.

Нам нужна новая таблица, в которой будут записаны StopTimes, если мы знаем DepartureTime. Мы начинаем с остановки в начале первого раздела в StopTime=DepartureTime, затем определяем RunningTime для каждого раздела на основе предыдущего StopTime и вставляем StopTime как новую строку. Это означает, что значения RunningTime и, следовательно, StopTime основаны на результатах, вычисленных для предыдущих строк.

В нашей текущей реализации КУРСОР используется для перебора разделов, сохранения текущего StopTime, проверки соответствующего окна на предмет следующего RunningTime, добавления RunningTime к StopTime и INSERT.

Упрощенная версия нашего кода такова:

CREATE TABLE #Sections
(
    SectionID INT PRIMARY KEY IDENTITY,
    SectionTypeID INT,
    StartPlaceID INT,
    EndPlaceID INT,
    [Order] INT,
);

CREATE TABLE #SectionTimeWindows
(
    SectionTimeWindowID INT PRIMARY KEY IDENTITY,
    SectionTypeID INT,
    WindowStart INT,
    WindowEnd INT,
    RunningTime INT,
)

CREATE TABLE #StopTimes
(
    StopTimeID INT PRIMARY KEY IDENTITY,
    PlaceID INT,
    IncommingSectionID INT NULL,
    ArrivalTime INT,
    [Order] INT,
)

INSERT INTO #Sections (SectionTypeID, StartPlaceID, EndPlaceID, [Order]) VALUES 
    (1, 10, 20, 1),
    (1, 20, 30, 2),
    (1, 30, 40, 3),
    (1, 40, 50, 4),
    (1, 50, 60, 5)

INSERT INTO #SectionTimeWindows (SectionTypeID, WindowStart, WindowEnd, RunningTime) VALUES
    (1, 00*60, 09*60, 30), -- from midnight to 9AM 
    (1, 09*60, 18*60, 50), -- from 9AM to 18AM    
    (1, 18*60, 24*60, 5) --- dtto

DECLARE @Departure INT = 8 * 60 -- the departure of the whole movement. Used at the first iteration only. This is a parameter of this (when this is converted to SP)
DECLARE @CurrentTime INT = NULL -- the stop time variable hold the current result of current interation
DECLARE @RunningTime INT = NULL -- the current iteration running time, based on window

DECLARE @CurrentSectionID INT;
DECLARE @CurrentSectionTypeID INT;
DECLARE @CurrentStartPlaceID INT;
DECLARE @CurrentEndPlaceID INT;

DECLARE @Order INT = 1;

DECLARE rowIterator CURSOR
    LOCAL FAST_FORWARD FOR
        SELECT SectionID, SectionTypeID, StartPlaceID, EndPlaceID
        FROM #Sections
        ORDER BY [Order]
OPEN rowIterator;

WHILE (1=1)
BEGIN
    FETCH NEXT FROM rowIterator INTO @CurrentSectionID, @CurrentSectionTypeID, @CurrentStartPlaceID, @CurrentEndPlaceID
    IF (@@FETCH_STATUS <> 0) BREAK;
        
    IF (@CurrentTime is null) -- First iteration, we have no previous results, need @Departure as first StopTime
    BEGIN 
        SET @CurrentTime = @Departure
        INSERT INTO #StopTimes (PlaceID, IncommingSectionID, ArrivalTime, [Order]) 
        VALUES (@CurrentStartPlaceID, NULL, @CurrentTime, @Order)
    END

    SET @Order = @Order + 1
    -- Fetch @RunningTime from signle window where @CurrentStopTime is in the window and SectionType matches
    SELECT TOP 1 @RunningTime = RunningTime FROM #SectionTimeWindows 
        WHERE SectionTypeID = @CurrentSectionTypeID AND WindowStart <= @CurrentTime AND @CurrentTime < WindowEnd
    -- Add @RunningTime to @CurrentStopTime. This value will be used in next iteration to get new window
    SET @CurrentTime = @CurrentTime + @RunningTime
    INSERT INTO #StopTimes (PlaceID, IncommingSectionID, ArrivalTime, [Order])
        VALUES (@CurrentEndPlaceID, @CurrentSectionID, @CurrentTime, @Order)
END
CLOSE rowIterator
DEALLOCATE rowIterator

SELECT StopTimeID, PlaceID, IncommingSectionID, 
ArrivalTime / 60 as Hours, ArrivalTime % 60 as Minutes,
ArrivalTime - LAG(ArrivalTime) Over (ORDER BY [Order]) as RunningTime
FROM #StopTimes


DROP TABLE #Sections
DROP TABLE #StopTimes
DROP TABLE #SectionTimeWindows

Реальная структура немного сложнее, данные также группируются по MovementID и помимо ArrivalTime мы отслеживаем еще несколько свойств. Мы также можем подождать на StopPlace, что также задерживает отправление на следующий участок. Но в этом вся суть.

Вычисление реальной процедуры занимает до 6 часов (реальная таблица разделов содержит около 10 ^ 6 строк, а таблицаsectionTimeWindows — около 10 ^ 7 строк). Проблема, скорее всего, заключается в SELECT на каждой итерации FETCH, но я не знаю, как ПРИСОЕДИНИТЬСЯ к РазделTimeWindows, если у меня нет зонда для этого.

Я пытался решить эту проблему с помощью LAG, но ничего не добился, поскольку ArrivalTime предыдущей строки является рекурсивным результатом ее прибытия предыдущей строки. У меня та же проблема с SUM OVER(PARTITION..RANGE..), но опять же, я понятия не имею, как ПРИСОЕДИНИТЬСЯ к окну. Мне понадобится какая-то СУММА (ВЫБРАТЬ... ГДЕ ЗОНД)...

Есть ли способ удалить такой КУРСОР? А если нет, то есть ли способ ускорить SELECT TOP 1?

Вот, надеюсь, более четкое объяснение того, что должна делать функция:

  1. У нас есть набор разделов, каждый раздел имеет набор секцийTimeWindows, где каждое окно определяет интервал времени, в течение которого действительно время выполнения в разделе. Останавливаемся между секциями. Если мы прибудем в секцию между 00:00 и 9:00, Время выполнения в секции составит 30 минут; если мы приедем с 9:00 до 18:00, время работы составит 50 минут и т. д.
  2. Прибытие в раздел фиксируется в таблице StopTimes, столбец ArrivalTime. Существует N+1 StopTimes, поскольку мы также останавливаемся перед первым разделом.
  3. Прибытие в раздел определяется параметром ArrivalTime в предыдущий раздел + Время выполнения, заданное параметром DivisionTimeWindows, где Время прибытия в предыдущем разделе попадает в окно <WindowStart, WindowEnd)

если вам нужна информация из предыдущей строки, почему бы не использовать LAG? Какую версию SQL Server вы используете?

Thom A 08.08.2024 12:40

Есть ли у вас правильные индексы, чтобы ТОП-1 хотя бы не просматривал всю таблицу? Сколько строкsectionTimeWindows в каждом разделе? Всегда ли 3?

siggemannen 08.08.2024 12:43

Часть этой логики кажется запутанной. Например, у вас есть пункт WindowStart <= @Probe. @Prode определяется как @StopTime, а @StopTime определяется как @Departure. Почему бы просто не WindowStart <= @Departure?

Thom A 08.08.2024 13:18

Кроме того, ваш запрос с TOP не имеет ORDER BY, поэтому значение, присвоенное @RunningTime, будет совершенно произвольным. Я чувствую, что вам нужно сделать шаг назад и объяснить реальную логику перехода от А к Б.

Thom A 08.08.2024 13:21

Кажется, интервалы SectionTypeID в #SectionTimeWindowstable никогда не пересекаются. Вам не нужно Top(1).

Serg 08.08.2024 13:28

@ThomA Информация взята из предыдущей строки результата StopTimes, а не из предыдущей строки входных разделов; @StopTime меняется на каждой итерации. @Departure используется только в первой итерации, а @Probe — это просто псевдоним. Я добавил @Probe для краткости и потому, что в реальной реализации @Probe необходим, так как есть также @StopTime и @WaitTime. Это может быть WindowStart <= @StopTime, да. Для TOP 1 не требуется никакого ORDER BY, поскольку для каждогоsectionTypeID существует только одно окно, куда попадает @Probe. Оно существует, потому что оно ускоряет выбор.

David Cholt 08.08.2024 13:32

@siggemannen Да, индекс есть, иначе все это было бы бесполезно. Я думаю, проблема в том, что даже с индексом итеративный характер курсора делает его медленнее, поскольку он будет выполнять безумное количество поисков по индексу, и я думаю, что если бы объединение было возможно, сканирование индекса здесь было бы на самом деле лучше, возможно?

David Cholt 08.08.2024 13:35

Без описания логики я не буду пытаться написать попытку, основанную на множествах. Как я уже говорил, происходит сильное «мутение воды» (@Probe = @StopTime = @Departure = 8 * 60 — это всего лишь пример). Если вы потратите время на объяснение цели, то выработать комплексное решение, скорее всего, будет намного проще.

Thom A 08.08.2024 13:38

Разве @Probe не увеличивается все время? Должен ли быть какой-то сброс, когда дело доходит до следующего дня? Или я что-то упускаю?

siggemannen 08.08.2024 14:03

@ThomA Я немного обновил код, добавил комментарии и описание того, что должна делать логика.

David Cholt 08.08.2024 14:04

@siggemannen На самом деле это так, есть родительский объект, который сбрасывает счетчик времени. Я пропустил это, поскольку это не имеет значения, и я могу РАЗДЕЛИТЬ реализацию этого на основе набора, если это возможно.

David Cholt 08.08.2024 14:06

Это важно, потому что через некоторое время вы не получите никаких попаданий в таблицу #windows, что искажает результаты, по крайней мере, когда я тестирую. Кстати, это, наверное, глупо, но вы использовали: SET NOCOUNT ON при выполнении этой процедуры? С такой настройкой такие курсоры занимают вечность OFF. Может ли это движение длиться несколько дней?

siggemannen 08.08.2024 14:08

@siggemannen да, я использую SET NOCOUNT ON и да, в этом примере окна закончатся, но в реальной реализации есть функция по модулю - когда мы приходим в 24:00, мы переносимся на 00:00. К сожалению, я не могу показать исходный код :( Я попытался выделить соответствующую часть моей проблемы.

David Cholt 08.08.2024 14:15

Я предполагаю, что ты по модулю этой штучки-зонда сохранишь время окончания как есть? Спонтанно, я не думаю, что так просто отойти курсором, но вам придется каждый день пересчитывать эту штуку с нуля?

siggemannen 08.08.2024 14:23

@siggemannen Я по модулю зонда, да, тогда я всегда получаю окно и всегда получаю RunningTime. Однако время прибытия постоянно увеличивается (без модуля) и может достигать нескольких дней, накапливая все значения времени выполнения. Мы пересчитываем его каждый раз, когда происходит миграция данных из старой системы, что в настоящее время происходит ежедневно. Процесс занимает до 6 часов (на плохом оборудовании я ничего не могу поделать. Локально это занимает 45 минут)

David Cholt 08.08.2024 14:28

сколько существует различных вариантов окон? Возможно, там можно что-то сделать, чтобы избежать выбора? Например, можно сопоставить различные варианты и вместо этого объединиться против них.

siggemannen 08.08.2024 14:29

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

David Cholt 08.08.2024 14:36

Хорошо, я сдаюсь :) Купите приличный компьютер, создайте к нему связанный сервер, запустите все там, а затем скопируйте обратно на «слабую» машину. Время разработки намного дороже, чем оборудование

siggemannen 08.08.2024 14:41

@siggemannen Хе-хе, да, мне бы хотелось, но у нас большой срок развертывания, поэтому время разработки ничего не значит :( В любом случае, большое спасибо за попытку, у меня было нутром предчувствие, что у меня есть один пример CURSOR, где CURSOR удалить... сложно, но я попробую предварительное объединение окон в курсор, предложенное Лайошем Арпадом ниже, думаю, это может немного помочь...

David Cholt 08.08.2024 14:46
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
19
98
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете присоединиться к #Sections с помощью #SectionTimeWindows:

        SELECT S.SectionID, S.SectionTypeID, S.StartPlaceID, S.EndPlaceID, STW.Runtime
        FROM #Sections S
        JOIN #SectionTimeWindows STW
        ON S.SectionTypeID = STW.SectionTypeID
        ORDER BY [Order]

а затем просмотреть результаты без поиска разницы в окне. Если у вас есть несколько записей окон одного типа, вы можете SUM(Runtime) AS Runtime и использовать GROUP BY.

Кроме того, вы даже можете избавиться от цикла, используя insert-select, но тогда вам нужно будет соединить текущий элемент со следующим и иметь левое соединение для промежуточного элемента, который будет иметь значение null (доказывая, что next действительно является следующим) и вставка первой специальной записи отдельно. Но это большое изменение. Если проблема с производительностью сохранится, дайте мне знать.

Наконец, рассмотрим создание индексов, ускоряющих поиск.

Вы имеете в виду объединение окон по определению CURSOR, а затем пропуск итераторов итератора, когда окно неверно в соответствии с CurrentTime? Я могу попробовать это, хотя я не уверен, как мне показать результат... Отредактируйте вопрос и добавьте новый код? Также я не понимаю, что сумма WindowEnd-WindowStart - RunningTime имеет значение, если мое CurrentTime попадает в окно. Разница (= длина окна) произвольна (определяется пользователем).

David Cholt 08.08.2024 14:18

Со вторым абзацем у меня возникли проблемы. Я хотел бы каким-то образом присоединиться к предыдущей строке результатов, но, поскольку эта строка зависит от всех предыдущих строк результатов, как мне это сделать? Какая-то рекурсия? Функция возврата? Индексы присутствуют, без них весь CURSOR в любом случае был бы непривычно медленным. Также есть «SET NOCOUNT ON».

David Cholt 08.08.2024 14:22

@DavidCholt, второй абзац посвящен агрегированию. Вы можете группировать и суммировать. Да, пятый зависит от четвертого, а четвертый зависит от третьего, у вас есть STW.WindowEnd - STW.WindowStart первого + STW.WindowEnd - STW.WindowStart второго, что сводится к SUM().

Lajos Arpad 08.08.2024 15:23

Я думаю тут недоразумение, WindowEnd-WindowStart просто дает мне WindowSize, который меня не волнует. Все, что меня волнует, это RunningTime из окон. Но я попытался объединить записи окон в разделы, поскольку выбор внутри фильтра, и логика теперь пропускает окна, которые не имеют значения при получении RunTime... Это работает, но, к сожалению, производительность та же самая :(

David Cholt 08.08.2024 17:17

@DavidCholt действительно, насколько я понимаю, WindowEnd - WindowStart - это продолжительность, то есть это моменты времени. Поскольку вас интересует Runtime, нам нужно использовать его вместо вычитания, которое я предлагал изначально. Отредактировал мой ответ соответственно.

Lajos Arpad 08.08.2024 18:32

Да, это то, что я реализовал, и это работает (т.е. дает правильные результаты), но производительность такая же или немного ниже. Но это подтвердило для меня, что выбор внутри CURSOR, по-видимому, не является проблемой, проблема в другом...

David Cholt 09.08.2024 09:38

@DavidCholt просто из любопытства: сколько времени занимает выполнение запроса, который вы можете найти в моем ответе?

Lajos Arpad 09.08.2024 09:50

Я не делал полный прогон для всей таблицы, только TOP(3000) движений, но оригинал занимает около 6 секунд, а предварительно объединенный курсор занимает 7-8 секунд (оба дают одинаковые результаты по 45 тысяч+, так что это хорошо). Оно того стоило, поскольку, похоже, повторяющиеся выборы в курсорном решении не являются узким местом. Я попробую изучить этот путь, также удерживая курсор, но, к сожалению, не могу отметить 2 ответа как принятые. Однако это было полезно. Спасибо!

David Cholt 09.08.2024 12:05

@DavidCholt, не беспокойся, рад помочь.

Lajos Arpad 09.08.2024 12:08
Ответ принят как подходящий

Расписание не зависит от мест остановок. Поэтому сначала делаем отдельное расписание (расписание) для каждогоsectionTypeId.

Из #SectionTimeWindows:

СекцияTimeWindowID ID разделатипа ОкноСтарт ОкноКонец Время выполнения 1 1 0 540 30 2 1 540 1080 50 3 1 1080 1440 5

К расписанию:
Преобразовано в расписание. Часть расписания дляsectionTypeId=1. Начинаем с 8:00.

уровень Число ID разделатипа ОкноСтарт ОкноКонец Время выполнения часы минуты 0 нулевой 1 нулевой 480 нулевой 8 0 1 0 1 480 510 30 8 30 2 1 1 510 540 30 9 0 3 2 1 540 590 50 9 50 4 3 1 590 640 50 10 40 5 4 1 640 690 50 11 30 6 5 1 690 740 50 12 20 7 6 1 740 790 50 13 10 8 7 1 790 840 50 14 0 9 8 1 840 890 50 14 50 10 9 1 890 940 50 15 40

Затем присоедините места (отсортированные по [Порядок]) к расписанию.
Некоторая манипуляция с начальной точкой немного усложняет (запутывает) запрос.

См. пример

Данные испытаний:

ID раздела ID разделатипа StartPlaceID EndPlaceID Заказ 1 1 10 20 1 2 1 20 30 2 3 1 30 40 3 4 1 40 50 4 5 1 50 60 5 6 2 11 21 1 7 2 21 31 2 8 2 31 41 3 9 2 41 51 4 10 2 51 61 5
СекцияTimeWindowID ID разделатипа ОкноСтарт ОкноКонец Время выполнения 1 1 0 540 30 2 1 540 1080 50 3 1 1080 1440 5 4 2 0 600 40 5 2 600 1080 60 6 2 1080 1440 20
with r as( -- recursively create timetable
select 0 lvl, null Num,SectionTypeID
  ,null WindowStart
  ,08*60  as WindowEnd  
  ,null RunningTime
from #SectionTimeWindows w
 where w.WindowStart=(select min(WindowStart) from #SectionTimeWindows w2
                    where w2.SectionTypeId=w.SectionTypeId)
  union all
select lvl+1,coalesce(r.Num,-1)+1,w.SectionTypeID
  ,r.WindowEnd WindowStart
  ,r.WindowEnd+w.RunningTime WindowEnd
  ,w.RunningTime
from r inner join #SectionTimeWindows w 
  on r.SectionTypeId=w.SectionTypeId
    and w.WindowStart<=r.WindowEnd and w.WindowEnd>r.WindowEnd 
-- where lvl<17  -- for debug only
)
select 
   s.SectionTypeID, WindowStart, WindowEnd, RunningTime
  ,case when num is null then StartPlaceID else EndPlaceId end PlaceId
  ,case when num is null then null else SectionId end IncomingSectionId
  ,WindowEnd/60 hours
  ,WindowEnd%60 minutes
from r 
inner join (select *, row_number()over(partition by SectionTypeId order by [Order] )rn
      from #Sections) s 
  on r.SectionTypeId=s.SectionTypeId 
    and (r.Num=(s.rn-1) or (r.num is null and rn=1))
order by s.SectionTypeId,lvl;

Выход

ID разделатипа ОкноСтарт ОкноКонец Время выполнения Идентификатор места Inc.SectionId часы минуты 1 нулевой 480 нулевой 10 нулевой 8 0 1 480 510 30 20 1 8 30 1 510 540 30 30 2 9 0 1 540 590 50 40 3 9 50 1 590 640 50 50 4 10 40 1 640 690 50 60 5 11 30 2 нулевой 480 нулевой 11 нулевой 8 0 2 480 520 40 21 6 8 40 2 520 560 40 31 7 9 20 2 560 600 40 41 8 10 0 2 600 660 60 51 9 11 0 2 660 720 60 61 10 12 0

рабочий пример

Обновление 1.
Мы можем уменьшить глубину рекурсии.
Если размер окна (WindowEnd-WindowStart)>> RunningTime, мы должны осторожно брать строки рядом с WindowStart и WindowEnd с помощью рекурсии, а все остальные строки генерировать через generate_series.

-- timetable with  reduced recursion
with r as(
select 0 lvl, 1 repeatCnt, null Num,SectionTypeID
  ,null WindowStart
  ,08*60  as WindowEnd  
  ,null RunningTime
from #SectionTimeWindows w
 where w.WindowStart=(select min(WindowStart) from #SectionTimeWindows w2
                    where w2.SectionTypeId=w.SectionTypeId)
  union all
select lvl+1
  ,case when (w.WindowEnd-r.WindowEnd)>w.runningTime then -- multy row
      floor((w.WindowEnd-r.WindowEnd)/w.runningTime)
   else 1 
   end repeatCnt
  ,coalesce(r.Num,-1)+1,w.SectionTypeID
  ,r.WindowEnd WindowStart
  ,case when (w.WindowEnd-r.WindowEnd)>w.runningTime then -- multy row
      r.WindowEnd+floor((w.WindowEnd-r.WindowEnd)/w.runningTime)*w.runningTime
   else r.WindowEnd+w.RunningTime 
   end WindowEnd
  ,w.RunningTime
from r inner join #SectionTimeWindows w 
  on r.SectionTypeId=w.SectionTypeId
    and w.WindowStart<=r.WindowEnd and w.WindowEnd>r.WindowEnd 
where lvl<17
)
,ExpandedTimetable as(
  select lvl,repeatCnt,Num,SectionTypeID
    ,WindowStart+RunningTime*(value-1) WindowStart
    ,WindowStart+RunningTime*(value) WindowEnd
    ,WindowStart grStart, WindowEnd grEnd
    ,RunningTime
    ,value n
  from r
  cross apply generate_series(1,r.repeatCnt)tn
  )
 select * from r order by SectionTypeId,lvl;
 -- select * from ExpandedTimeTable order by SectionTypeId,lvl;

  select * 
  ,WindowEnd/60 hours
  ,WindowEnd%60 minutes
  from ExpandedTimeTable order by SectionTypeId,lvl;

Вывод рекурсивного запроса составляет всего несколько строк. Глубина рекурсии равна 4.

уровень повторCnt Число ID разделатипа ОкноСтарт ОкноКонец Время выполнения 0 1 нулевой 1 нулевой 480 нулевой 1 2 0 1 480 540 30 2 10 1 1 540 1040 50 3 1 2 1 1040 1090 50 4 70 3 1 1090 1440 5 0 1 нулевой 2 нулевой 480 нулевой 1 3 0 2 480 600 40 2 8 1 2 600 1080 60 3 18 2 2 1080 1440 20

Тогда мы сможем расширить график. В каждой строке до repeatNum строк.

уровень повторCnt Число ID разделатипа ОкноСтарт ОкноКонец грСтарт грЭнд Время выполнения н 0 1 нулевой 1 нулевой нулевой нулевой 480 нулевой 1 1 2 0 1 480 510 480 540 30 1 1 2 0 1 510 540 480 540 30 2 2 10 1 1 540 590 540 1040 50 1 2 10 1 1 590 640 540 1040 50 2 2 10 1 1 640 690 540 1040 50 3 2 10 1 1 690 740 540 1040 50 4 2 10 1 1 740 790 540 1040 50 5 2 10 1 1 790 840 540 1040 50 6 2 10 1 1 840 890 540 1040 50 7 2 10 1 1 890 940 540 1040 50 8 2 10 1 1 940 990 540 1040 50 9 2 10 1 1 990 1040 540 1040 50 10 3 1 2 1 1040 1090 1040 1090 50 1 4 70 3 1 1090 1095 1090 1440 5 1 4 70 3 1 1095 1100 1090 1440 5 2 4 70 3 1 1100 1105 1090 1440 5 3 4 70 3 1 1105 1110 1090 1440 5 4

рабочий пример

По сути, это то, что я и предполагал — своего рода рекурсия на основе наборов для возврата данных. Я опробовал версию этой идеи на наших реальных данных (поскольку полное решение потребует полной переработки SP). Рекурсия настолько глубока, что не удается достичь предела рекурсии, поскольку в реальных данных время выполнения намного меньше → больше итераций рекурсии для вычисления расписания → предел рекурсии. Но я могу использовать фильтр отладки на уровне, чтобы получить первые 20 строк рекурсивного расписания для тестирования.

David Cholt 09.08.2024 11:45

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

David Cholt 09.08.2024 11:45

Тем не менее, мне удалось его немного настроить и получить некоторые результаты, однако, даже для одного движения и ограниченных уровней рекурсии, существует слишком много рекурсий, а таблица огромна (поскольку она в основном содержит все возможные RunTime за любое возможное время, когда мы можем прибыть в секцию). К сожалению, запрос выполняется на несколько порядков медленнее. Тем не менее, этот подход верен, и я собираюсь изучить его подробнее. В этом ответе также показан способ избежать курсора, поэтому я отмечу его. Спасибо!

David Cholt 09.08.2024 11:51

Вы можете значительно уменьшить глубину рекурсии. Минимальная глубина рекурсии близка к числу строк в таблице #SectionTimeWindows. См. пример с уменьшенной глубиной рекурсии.

ValNik 09.08.2024 13:37

Другие вопросы по теме