SQL Server CTE и пример рекурсии

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

WITH
  cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
  AS
  (
    SELECT EmployeeID, FirstName, LastName, ManagerID, 1
    FROM Employees
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
      r.EmpLevel + 1
    FROM Employees e
      INNER JOIN cteReports r
        ON e.ManagerID = r.EmpID
  )
SELECT
  FirstName + ' ' + LastName AS FullName,
  EmpLevel,
  (SELECT FirstName + ' ' + LastName FROM Employees
    WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID

Здесь я размещаю информацию о том, как выводится вывод: enter image description here

Мне просто нужно знать, как он показывает менеджера сначала, а затем его подчиненный в цикле. Я предполагаю, что первый оператор sql срабатывает только один раз и возвращает все идентификаторы сотрудников.

И второй запрос повторно срабатывает, запрашивая базу данных, в которой сотрудник существует с текущим идентификатором менеджера.

Просьба пояснить, как выполняется оператор sql во внутреннем цикле, а также указать порядок выполнения sql. Спасибо.

Моя вторая фаза вопроса

;WITH Numbers AS
(
    SELECT n = 1
    UNION ALL
    SELECT n + 1
    FROM Numbers
    WHERE n+1 <= 10
)
SELECT n
FROM Numbers

Q 1) как увеличивается значение N? если значение присваивается N каждый раз, тогда значение N может быть увеличено, но только начальное значение N было инициализировано.

Q 2) CTE и рекурсия отношений сотрудников:

В тот момент, когда я добавляю двух менеджеров и добавляю еще несколько сотрудников под вторым менеджером, проблема начинается.

Я хочу отобразить первую подробную информацию о менеджере, а в следующих строках - только те данные сотрудников, которые относятся к подчиненному этому менеджеру.

Пусть

ID     Name      MgrID    Level
---    ----      ------   -----
1      Keith      NULL     1
2      Josh       1        2
3      Robin      1        2
4      Raja       2        3
5      Tridip     NULL     1
6      Arijit     5        2
7      Amit       5        2
8      Dev        6        3

Я хочу показать результаты таким образом с выражениями CTE. Скажите, пожалуйста, что изменить в моем sql, который я здесь привел, чтобы вытащить отношения между менеджером и сотрудником. Спасибо.

Я хочу, чтобы результат был таким:

ID          Name   MgrID       nLevel      Family
----------- ------ ----------- ----------- --------------------
1           Keith  NULL        1           1
3           Robin  1           2           1
2           Josh   1           2           1
4           Raja   2           3           1
5           Tridip NULL        1           2
7           Amit   5           2           2
6           Arijit 5           2           2
8           Dev    6           3           2

Возможно ли это??

Ответ 1

Я не тестировал ваш код, просто попытался помочь вам понять, как он работает в комментарии;

WITH
  cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
  AS
  (
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
-- In a rCTE, this block is called an [Anchor]
-- The query finds all root nodes as described by WHERE ManagerID IS NULL
    SELECT EmployeeID, FirstName, LastName, ManagerID, 1
    FROM Employees
    WHERE ManagerID IS NULL
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
    UNION ALL
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>    
-- This is the recursive expression of the rCTE
-- On the first "execution" it will query data in [Employees],
-- relative to the [Anchor] above.
-- This will produce a resultset, we will call it R{1} and it is JOINed to [Employees]
-- as defined by the hierarchy
-- Subsequent "executions" of this block will reference R{n-1}
    SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
      r.EmpLevel + 1
    FROM Employees e
      INNER JOIN cteReports r
        ON e.ManagerID = r.EmpID
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>
  )
SELECT
  FirstName + ' ' + LastName AS FullName,
  EmpLevel,
  (SELECT FirstName + ' ' + LastName FROM Employees
    WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID

Самый простой пример рекурсивного CTE, который я могу представить, чтобы проиллюстрировать его работу:

;WITH Numbers AS
(
    SELECT n = 1
    UNION ALL
    SELECT n + 1
    FROM Numbers
    WHERE n+1 <= 10
)
SELECT n
FROM Numbers

Q 1), как увеличивается значение N. если значение присваивается N каждый раз, тогда значение N может увеличиваться, но только первое значение N инициализируется.

A1: В этом случае N не является переменной. N является псевдонимом. Это эквивалентно SELECT 1 AS N. Это синтаксис личных предпочтений. В CTE в T-SQL есть 2 основных метода столбцов сложения. Я включил аналог простого CTE в Excel, чтобы попытаться проиллюстрировать более знакомым способом то, что происходит.

--  Outside
;WITH CTE (MyColName) AS
(
    SELECT 1
)
-- Inside
;WITH CTE AS
(
    SELECT 1 AS MyColName
    -- Or
    SELECT MyColName = 1  
    -- Etc...
)

Excel_CTE

Q 2) теперь здесь о CTE и рекурсии отношения сотрудников в тот момент, когда я добавляю двух менеджеров и добавляю еще несколько сотрудников под вторым менеджером, тогда проблема начинается. я хочу отобразить детали первого менеджера, а в следующих строках будут указаны только те детали сотрудников, которые подчиняются этому менеджеру

A2:

Отвечает ли этот код на ваш вопрос?

--------------------------------------------
-- Synthesise table with non-recursive CTE
--------------------------------------------
;WITH Employee (ID, Name, MgrID) AS 
(
    SELECT 1,      'Keith',      NULL   UNION ALL
    SELECT 2,      'Josh',       1      UNION ALL
    SELECT 3,      'Robin',      1      UNION ALL
    SELECT 4,      'Raja',       2      UNION ALL
    SELECT 5,      'Tridip',     NULL   UNION ALL
    SELECT 6,      'Arijit',     5      UNION ALL
    SELECT 7,      'Amit',       5      UNION ALL
    SELECT 8,      'Dev',        6   
)
--------------------------------------------
-- Recursive CTE - Chained to the above CTE
--------------------------------------------
,Hierarchy AS
(
    --  Anchor
    SELECT   ID
            ,Name
            ,MgrID
            ,nLevel = 1
            ,Family = ROW_NUMBER() OVER (ORDER BY Name)
    FROM Employee
    WHERE MgrID IS NULL

    UNION ALL
    --  Recursive query
    SELECT   E.ID
            ,E.Name
            ,E.MgrID
            ,H.nLevel+1
            ,Family
    FROM Employee   E
    JOIN Hierarchy  H ON E.MgrID = H.ID
)
SELECT *
FROM Hierarchy
ORDER BY Family, nLevel

Еще один sql с древовидной структурой

SELECT ID,space(nLevel+
                    (CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
                )+Name
FROM Hierarchy
ORDER BY Family, nLevel

Ответ 2

Хотелось бы наметить краткую семантическую параллель с уже правильным ответом.

В "простых" терминах рекурсивный CTE может быть семантически определен как следующие части:

1: запрос CTE. Также известен как ANCHOR.

2: Рекурсивный запрос CTE на CTE в (1) с UNION ALL (или UNION или EXCEPT или INTERSECT), поэтому возвращается окончательный результат.

3: Условие угла/завершения. Это по умолчанию, когда больше нет строк/кортежей, возвращаемых рекурсивным запросом.

Краткий пример, который сделает снимок прозрачным:

;WITH SupplierChain_CTE(supplier_id, supplier_name, supplies_to, level)
AS
(
SELECT S.supplier_id, S.supplier_name, S.supplies_to, 0 as level
FROM Supplier S
WHERE supplies_to = -1    -- Return the roots where a supplier supplies to no other supplier directly

UNION ALL

-- The recursive CTE query on the SupplierChain_CTE
SELECT S.supplier_id, S.supplier_name, S.supplies_to, level + 1
FROM Supplier S
INNER JOIN SupplierChain_CTE SC
ON S.supplies_to = SC.supplier_id
)
-- Use the CTE to get all suppliers in a supply chain with levels
SELECT * FROM SupplierChain_CTE

Объяснение: Первый запрос CTE возвращает базовых поставщиков (например, листья), которые не поставляют никому другому поставщику напрямую (-1)

Рекурсивный запрос в первой итерации получает всех поставщиков, которые поставляют поставщикам, возвращенным ANCHOR. Этот процесс продолжается до тех пор, пока условие не вернет кортежи.

UNION ALL возвращает все кортежи по общим рекурсивным вызовам.

Еще один хороший пример можно найти здесь.

PS: Для работы рекурсивного CTE отношения должны иметь иерархическое (рекурсивное) условие для работы. Ex: elementId = elementParentId.. вы получаете точку.

Ответ 3

Процесс выполнения действительно путается с рекурсивным CTE, я нашел лучший ответ на https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx, и реферат процесса выполнения CTE как показано ниже.

Семантика рекурсивного выполнения выглядит следующим образом:

  • Разделите выражение CTE на элементы привязки и рекурсии.
  • Запустите анкерный элемент (ы), создающий первый набор или базовый результирующий набор (T0).
  • Запустите рекурсивный элемент с Ti в качестве входа и Ti + 1 в качестве вывода.
  • Повторите шаг 3 до тех пор, пока не будет возвращен пустой набор.
  • Возвращает набор результатов. Это UNION ALL от T0 до Tn.

Ответ 4

    --DROP TABLE #Employee
    CREATE TABLE #Employee(EmpId BIGINT IDENTITY,EmpName VARCHAR(25),Designation VARCHAR(25),ManagerID BIGINT)

    INSERT INTO #Employee VALUES('M11M','Manager',NULL)
    INSERT INTO #Employee VALUES('P11P','Manager',NULL)

    INSERT INTO #Employee VALUES('AA','Clerk',1)
    INSERT INTO #Employee VALUES('AB','Assistant',1)
    INSERT INTO #Employee VALUES('ZC','Supervisor',2)
    INSERT INTO #Employee VALUES('ZD','Security',2)


    SELECT * FROM #Employee (NOLOCK)

    ;
    WITH Emp_CTE 
    AS
    (
        SELECT EmpId,EmpName,Designation, ManagerID
              ,CASE WHEN ManagerID IS NULL THEN EmpId ELSE ManagerID END ManagerID_N
        FROM #Employee  
    )
    select EmpId,EmpName,Designation, ManagerID
    FROM Emp_CTE
    order BY ManagerID_N, EmpId