Я полагаю, что вы можете сделать это с помощью рекурсивного выражения общей таблицы следующим образом, особенно если вы не ожидаете очень длинных цепочек записей:
WITH Ancestors AS
(
SELECT
InitRow.[ID] AS [Ancestor],
InitRow.[ID],
InitRow.[first],
InitRow.[end],
0 AS [level],
'00000' + InitRow.[ID] AS [hacky_level_plus_ID]
FROM
YOUR_TABLE AS InitRow
WHERE
NOT EXISTS
(
SELECT * FROM YOUR_TABLE AS PrevRow
WHERE PrevRow.[end] = InitRow.[first]
)
UNION ALL
SELECT
ParentRow.Ancestor,
ChildRow.[ID],
ChildRow.[first],
ChildRow.[end],
ParentRow.level + 1 AS [level],
-- Avoids having to build the recursive structure more than once.
-- We know we will not be over 5 digits since CTEs have a recursion
-- limit of 32767.
RIGHT('00000' + CAST(ParentRow.level + 1 AS varchar(4)), 5)
+ ChildRow.[ID] AS [hacky_level_plus_ID]
FROM
Ancestors AS ParentRow
INNER JOIN YOUR_TABLE AS ChildRow
ON ChildRow.[first] = ParentRow.[end]
)
SELECT
Ancestors.Ancestor + '-' + SUBSTRING(MAX([hacky_level_plus_ID]),6,10) AS [IDs],
-- Without the [hacky_level_plus_ID] column, you need to do it this way:
-- Ancestors.Ancestor + '-' +
-- (SELECT TOP 1 Children.ID FROM Ancestors AS Children
-- WHERE Children.[Ancestor] = Ancestors.[Ancestor]
-- ORDER BY Children.[level] DESC) AS [IDs],
MIN(Ancestors.[first]) AS [first],
MAX(Ancestors.[end]) AS [end]
FROM
Ancestors
GROUP BY
Ancestors.Ancestor
-- If needed, add OPTION (MAXRECURSION 32767)
Краткое объяснениечто делает каждая часть:
Предложение WITH Ancestors AS (...)
создает общее табличное выражение (в основном подзапрос) с именем Ancestors
.Первый SELECT
в этом выражении устанавливает базовую линию: все строки, у которых нет соответствующей записи до него.
Затем во второй SELECT
начинается рекурсия. Так как он ссылается на Ancestors
как часть запроса, он использует строки, которые он уже добавил в таблицу, а затем выполняет соединение с новыми из YOUR_TABLE
.Это будет рекурсивно находить все больше и больше строк для добавления в конец каждой цепочки.
Последнее предложение - SELECT
, которое использует эту рекурсивную таблицу, которую мы создали.Это просто GROUP BY
, так как мы сохранили исходный идентификатор в столбце Ancestor
, поэтому начало и конец - это простые MIN
и MAX
.
Сложная фигураидентификатор последней строки в цепочке.Есть два способа сделать это, оба показаны в запросе.Вы можете либо присоединиться к рекурсивной таблице, в этом случае она будет заново строить рекурсивную таблицу, либо вы можете попытаться отслеживать последний элемент по ходу работы.(Если создание рекурсивного списка связанных записей стоит дорого, вы определенно хотите минимизировать количество раз, которое вам нужно сделать.)
Способ, которым он отслеживает ход, состоит в том, чтобы отслеживать его положение вцепочка (столбец level
- обратите внимание, как мы добавляем 1 каждый раз, когда мы повторяем), обнуляем ее, а затем вставляем идентификатор в конце.Затем получение элемента с максимальным значением level
- это просто MAX
с последующим извлечением данных level
.
Если CTE придется слишком много рекурсировать, это вызовет ошибку, но яполагаю, что вы можете настроить это, используя опцию MAXRECURSION
.По умолчанию установлено значение 100. Если вам нужно установить его выше этого значения, вы можете рассмотреть возможность не использовать для этого рекурсивный CTE.
Это также не очень хорошо обрабатывает искаженные данные.Если у вас есть две записи с одинаковым first
или записью, в которой first
== end
, то это не будет работать правильно, и вам, возможно, придется настроить условия соединения внутри CTE или использовать другой подход.
Это не единственный способ сделать это.Я считаю, что было бы легче следовать, если бы вы создали пользовательскую процедуру и сделали все шаги вручную.Но у этого есть преимущество работы в одном выражении.