Концепция рекурсивного CTE - PullRequest
1 голос
/ 02 мая 2019

Я пытаюсь понять принципы использования CTE в моем коде SQL.Я просмотрел несколько постов в Интернете, объясняющих эту концепцию, но я не могу понять, как она повторяется для представления иерархических данных.Одним из широко используемых примеров для объяснения R-CTE является Пример Employee и ManagerID, как показано ниже:

USE AdventureWorks
GO
WITH Emp_CTE AS (
  SELECT EmployeeID, ContactID, LoginID, ManagerID, Title, BirthDate
  FROM HumanResources.Employee
  WHERE ManagerID IS NULL

  UNION ALL

  SELECT e.EmployeeID, e.ContactID, e.LoginID, e.ManagerID, e.Title, e.BirthDate
  FROM HumanResources.Employee e
  INNER JOIN Emp_CTE ecte ON ecte.EmployeeID = e.ManagerID
)
SELECT *
FROM Emp_CTE
GO

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

Ответы [ 3 ]

3 голосов
/ 02 мая 2019

Итак, вы хотите понять рекурсивный CTE.

Это действительно просто.

Сначала есть начальный запрос, который получает исходные записи.
В вашем случае это сотрудники без менеджера.
Кто будет боссом)

Для демонстрации на упрощенном примере:

EmployeeID LoginID ManagerID Title 
---------- ------- --------- ------------
101        boss    NULL      The Boss

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

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

Для первой итерации этого рекурсивного цикла вы можете получить что-то вроде этого:

 EmployeeID LoginID ManagerID Title 
---------- ------- --------- ------------
102        head1    101      Top Manager 1
103        head2    101      Top Manager 2

Для второй итерации она будет использовать записи из этой первой итерации, чтобы найти следующую.

 EmployeeID LoginID ManagerID Title 
---------- ------- --------- ------------

104        bob     102       Department Manager 1
105        hilda   102       Department Manager 2

108        john    103       Department Manager 4
109        jane    103       Department Manager 5

Для 3-й итерации он будет использовать записи из 2-й итерации.

...

И это продолжается до тех пор, пока не останется больше сотрудников, присоединившихся к ManagerID

Затем, после всех циклов, CTE вернет все записи, которые былинайдено через все эти итерации.

1 голос
/ 02 мая 2019

Ну, краткое введение в рекурсивные 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
0 голосов
/ 02 мая 2019

Это все о рекурсивном шаге: во-первых, root используется для продолжения первого шага рекурсии, поэтому:

SELECT EmployeeID, ContactID, LoginID, ManagerID, Title, BirthDate
FROM HumanResources.Employee
WHERE ManagerID IS NULL

Это обеспечивает первый набор записей.

Второй набор записей будетзапрашиваться на основе первого набора (якоря), поэтому он будет запрашивать всех сотрудников, у которых есть менеджер в первом наборе.

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

Третий шаг будет основан на третьем наборе результатов и т. Д.

...