Ну, краткое введение в рекурсивные CTE:
Рекурсивный CTE - это скорее итеративный, чем рекурсивный.Якорный запрос берется для получения некоторого начального набора результатов.С этим набором мы можем погрузиться глубже.Попробуйте следующие простые случаи:
Просто счетчик, даже не требуется JOIN ...
1 якоря приведет к 2 в UNION ALL
.Это 2 снова передается в UNION ALL и будет возвращено как 3 и так далее ...
WITH recCTE AS
(
SELECT 1 AS Mycounter
UNION ALL
SELECT recCTE.MyCounter+1
FROM recCTE
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE;
Счетчик из 2 столбцов
Это точно так же, как указано выше.Но у нас есть два столбца, и мы работаем с ними по отдельности.
WITH recCTE AS
(
SELECT 1 AS Mycounter1, 10 AS MyCounter2
UNION ALL
SELECT recCTE.MyCounter1+1,recCTE.MyCounter2+1
FROM recCTE
WHERE recCTE.MyCounter1<10
)
SELECT * FROM recCTE;
Теперь у нас есть две строки в начальном запросе
При выполнении в одиночку, первоначальный запрос вернет две строки.И со счетчиком == 1, и с двумя разными значениями для Nmbr-столбца
WITH recCTE AS
(
SELECT MyCounter=1, Nmbr FROM(VALUES(1),(10)) A(Nmbr)
UNION ALL
SELECT recCTE.MyCounter+1, recCTE.Nmbr+1
FROM recCTE
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE ORDER BY MyCounter,Nmbr;
Теперь мы получаем 20 строк назад, а не 10, как в предыдущих примерах.Это потому, что обе строки якоря используются независимо.
Мы можем использовать рекурсивный CTE в JOIN
. В этом примере мы сначала создадим производный набор, затем присоединимся к этому.к рекурсивному CTE.Угадайте, почему в первом ряду вместо буквы «А» стоит буква «Х»?
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A'),(2,'B'),(3,'C'),(4,'D'),(5,'E'),(6,'F'),(7,'G'),(8,'H'),(9,'I'),(10,'J')) A(id,Letter))
,recCTE AS
(
SELECT MyCounter=1, Nmbr,'X' AS Letter FROM(VALUES(1),(10)) A(Nmbr)
UNION ALL
SELECT recCTE.MyCounter+1, recCTE.Nmbr+1, SomeSet.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.id=recCTE.MyCounter+1
WHERE recCTE.MyCounter<10
)
SELECT * FROM recCTE ORDER BY MyCounter,Nmbr;
При этом будет использоваться самообращающееся соединение для имитации иерархии, но с одной цепочкой без пробелов
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',3),(5,'E',4),(6,'F',5),(7,'G',6),(8,'H',7),(9,'I',8),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE:
И теперь почти так же, как и раньше, но с несколькими элементами с тем же «предыдущим».
Это - в принципе - ваша иерархия
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',2),(5,'E',2),(6,'F',3),(7,'G',3),(8,'H',4),(9,'I',1),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE
Заключение
Ключевые точки
- Запрос привязки должен возвращать хотя бы одну строку, но может возвращать много
- Вторая часть должна соответствовать списку столбцов (как любой запрос
UNION ALL
) - Вторая часть должна ссылаться на cte в его
FROM
-классе - либо напрямую, либо
- через JOIN
- Вторая часть будет вызываться снова и снова с использованием результата вызова до
- Каждая строка обрабатывается отдельно ( скрытый RBAR )
- Вы можете начать сменеджера ( top-most-node ) и переходите к поиску сотрудников с этим идентификатором менеджера или
- You cначало с самой низкой в иерархии (те, где нет другой строки с использованием их идентификатора в качестве идентификатора менеджера) и перемещение вверх по списку
- , поскольку это скрытый RBAR вы можете использовать это для построчно действий, таких как кумуляция строк.
Пример последнего оператора
Посмотрите, как столбец LetterPathпостроен.
WITH SomeSet AS (SELECT * FROM (VALUES(1,'A',NULL),(2,'B',1),(3,'C',2),(4,'D',2),(5,'E',2),(6,'F',3),(7,'G',3),(8,'H',4),(9,'I',1),(10,'J',9)) A(id,Letter,Previous))
,recCTE AS
(
SELECT id,Letter,Previous,' ' PreviousLetter,CAST(Letter AS VARCHAR(MAX)) AS LetterPath FROM SomeSet WHERE Previous IS NULL
UNION ALL
SELECT SomeSet.id,SomeSet.Letter,SomeSet.Previous,recCTE.Letter,recCTE.LetterPath + SomeSet.Letter
FROM SomeSet
INNER JOIN recCTE ON SomeSet.Previous=recCTE.id
)
SELECT * FROM recCTE