#tsql #subquery #sql-server-2014 #gaps-and-islands
#tsql #подзапрос #sql-server-2014 #пробелы и острова
Вопрос:
У меня есть набор данных, в котором я должен объединить диапазоны для двух конкретных полей. Во время исследования я видел, что эта проблема называется «Острова и пробелы». К сожалению, большинство IAG, касающихся слияния между одним столбцом («StartDate») или что-то в этом роде. В то время как в моей проблеме у меня есть два столбца, которые составляют диапазон (PlzVon- PlzBis).
Я нашел множество примеров, но все они, как правило, повсюду со ссылкой на снятие шкуры с кошки. Некоторые из них используют CTE, другие используют ОТСТАВАНИЕ / ОПЕРЕЖЕНИЕ, о которых я никогда не слышал до сих пор.
Я попытался перепрофилировать один скрипт, который я нашел, в основном потому, что я могу понять, что он говорит, но без кубиков. Я вижу все «части» проблемы (поиск верхних / нижних конечных точек, сопряжение двух записей), но я не могу понять, как сформировать с ней согласованное утверждение.
На фотографии выше я хотел бы объединить выделенные строки так, чтобы они были PlzVon-73000 и PlzBis-74999. Я могу сказать, что мне нужен либо CTE, либо коррелированный подзапрос, и у меня есть условие b.PlzVon = a.PlzBis 1. Но одного соединения будет недостаточно, поскольку эта новая запись может затем соединиться с другой, что приведет нас к неприятному пути рекурсии и курсоров.
Любая помощь в выяснении того, как объединить эти острова, была бы весьма признательна.
Комментарии:
1. Что уникального в двух выделенных строках, которые можно использовать для определения того, что эти две строки должны быть объединены в одну строку? Что такого в первой строке с PlzVon-38000, что может привести к исключению ее из объединения с двумя другими строками?
2. Какую версию SQL Server вы используете? Пожалуйста, добавьте соответствующий тег.
3. Это не похоже
gaps-and-islands
на проблему. Взгляните на интервалы упаковки Ицика Бен-Гана. Если вы предоставите некоторые образцы данных в виде текста или в видеINSERT
утверждений (не изображений) в вопросе и вашем ожидаемом результате, есть большая вероятность, что кто-нибудь напишет рабочий запрос.4. @VladimirBaranov Это версия 2014, и я добавил тег, как вы просили.
5. @JohnH Поля ID_FI и ID_PE используются для идентификации «сущности», на которую они указывают. Я должен был добавить это к своему вопросу.
Ответ №1:
Если я правильно понимаю вопрос, то это проблема с островом данных, но в диапазоне, а не в ключе. Следующий запрос демонстрирует, как эта проблема может быть решена с использованием подхода острова данных.
DECLARE @SourceData TABLE
(
ID INT
,PlzVon INT
,PlzBis INT
)
INSERT INTO @SourceData
VALUES
(1,38000,38999),
(2,73000,73999),
(3,74000,74999),
(4,75000,75999),
(5,85000,85999);
;WITH CTE_DataIslands -- First CTE determine the start of each new data island
AS
(
SELECT [Main].ID
,[Main].[PlzVon]
,[Main].[PlzBis]
,(
CASE
WHEN (LAG([Main].[PlzBis], 1) OVER (ORDER BY [Main].[PlzVon] ASC) 1 ) <> ([Main].[PlzVon]) THEN 1 -- If prev record's value for ([PlzBis] 1) is not equal to current record [PlzVon] value then it is the start of a new data island.
ELSE 0
END
) AS [IsNewDataIsland]
FROM @SourceData [Main]
), CTE_GenerateGroupingID
AS
(
SELECT ID
,[PlzVon]
,[PlzBis]
,SUM([IsNewDataIsland]) OVER (ORDER BY [PlzVon] ROWS UNBOUNDED PRECEDING) AS GroupingID -- Create a running total of the IsNewDataIsland column this will create a grouping id we can now group on
FROM CTE_DataIslands
)
SELECT MIN([PlzVon]) AS [PlzVon] -- Min [PlzVon] will give the lower range
,MAX([PlzBis]) AS [PlzBis] -- Max [PlzBis] will give the upper range (use min or max for any other column that should be included in the return result)
FROM CTE_GenerateGroupingID
GROUP BY GroupingID
Ответ №2:
Мне было неясно, что такое PK, поэтому я добавил идентификатор в свою примерную таблицу
Declare @YourTable Table (ID int,PlzVon int,PlzBis int)
Insert Into @YourTable values
(1,38000,38999),
(1,73000,73999),
(1,74000,74999)
;with cte0(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N))
,cteN(N) As (Select Top (Select max(PlzBis)-min(PlzVon) 1 From @YourTable)
N=(Select min(PlzVon)-1 from @YourTable) Row_Number() over (Order By (Select NULL))
From cte0 N1, cte0 N2, cte0 N3, cte0 N4, cte0 N5, cte0 N6)
,cteBase As (Select A.ID
,A.PlzVon
,A.PlzBis
,PosNr = N.N
,RowNr = N.N - Row_Number() Over (Partition By A.ID Order By N.N)
From cteN N
Join @YourTable A on N.N Between A.PlzVon and A.PlzBis
)
Select ID
,PlzVon = min(PosNr)
,PlzBis = max(PosNr)
From cteBase
Group By ID,RowNr
Order By ID,min(PosNr)
ВОЗВРАТ
ID PlzVon PlzBis
1 38000 38999
1 73000 74999