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

109

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

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

Здесь я публикую о том, как отображается результат: введите описание изображения здесь

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

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

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

МОЯ 2-я фаза вопроса

;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

Это возможно...?

Томас
источник

Ответы:

210

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

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. Это синтаксис личных предпочтений. Есть 2 основных метода сглаживания столбцов в CTEin T-SQL. Я включил аналог простой 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
MarkD
источник
рекурсивный запрос CTE не возвращает результат так, как я хочу. Я хочу отобразить имя первого менеджера, а затем отобразить всех его подчиненных, снова отобразить имя второго менеджера, а затем отобразить всех его подчиненных. Я хочу, чтобы результат был таким. если возможно, обновите запрос ur. спасибо
Thomas
Добавлен столбец [Семья]. Проверь сейчас.
MarkD
здесь я даю результат так, как я хочу отобразить результат. пожалуйста, проверьте и скажите мне, возможно ли это ... если да, то внесите необходимые изменения в ur sql. спасибо за твои усилия.
Thomas
почему ';' перед оператором WITH? "; WITH" Спасибо
Дрюдин
2
@ SiKni8 - ссылка вроде мертвая
MarkD
11

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

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

1: запрос CTE. Также известен как ЯКОРЬ.

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 работал, отношения должны иметь иерархическое (рекурсивное) условие для работы. Пример: elementId = elementParentId .. вы поняли.

Vaibhav
источник
9

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

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

  1. Разделите выражение CTE на якорные и рекурсивные члены.
  2. Запустите элемент (ы) привязки, создав первый вызов или базовый набор результатов (T0).
  3. Запустите рекурсивный член (ы) с Ti в качестве входа и Ti + 1 в качестве выхода.
  4. Повторяйте шаг 3, пока не будет возвращен пустой набор.
  5. Верните набор результатов. Это СОЕДИНЕНИЕ ВСЕХ от 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
Вишал Мотвани
источник
1
Это ответ только для кода, который даже не отвечает на вопрос, поскольку в нем нет рекурсивного CTE.
Dragomok