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

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

мне просто нужно знать, как это показывает менеджер сначала, а затем его подчиненный в цикле. Я думаю, что первый 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

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

4 81

4 ответа:

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

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 основных метода сглаживания столбцов в CTE на 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

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

в "простых" терминах рекурсивный 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)

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

ОБЪЕДИНЕНИЕ ВСЕХ возвращает все Кортежи по общему количеству рекурсивных вызовов.

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

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

процесс выполнения действительно запутан с рекурсивным 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.
    --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