Przykład CTE i rekursji w SQL Server

109

Nigdy nie używam CTE z rekurencją. Właśnie czytałem artykuł na ten temat. W tym artykule przedstawiono informacje o pracownikach za pomocą CTE serwera Sql i rekurencji. Zasadniczo jest to pokazywanie pracownikom i ich menadżerom informacji. Nie jestem w stanie zrozumieć, jak działa to zapytanie. Oto zapytanie:

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

Tutaj piszę o tym, jak pokazuje się wyjście: wprowadź opis obrazu tutaj

Muszę tylko wiedzieć, jak to najpierw pokazuje menedżerowi, a potem w pętli swojemu podwładnemu. Wydaje mi się, że pierwsza instrukcja sql uruchamia się tylko raz i zwraca wszystkie identyfikatory pracowników.

Drugie zapytanie jest wielokrotnie uruchamiane, wysyłając zapytanie do bazy danych, w której znajduje się pracownik z bieżącym identyfikatorem menedżera.

Proszę wyjaśnić, w jaki sposób instrukcja sql wykonuje się w pętli wewnętrznej, a także podać kolejność wykonania sql. Dzięki.

MOJA druga faza pytań

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

P 1) W jaki sposób zwiększa się wartość N? jeśli wartość jest przypisywana do N za każdym razem, wówczas wartość N może być zwiększana, ale tylko przy pierwszej inicjalizacji wartości N.

Q 2) CTE i rekurencja relacji pracowniczych:

Problem zaczyna się w momencie, gdy dodam dwóch menedżerów i kilku pracowników pod drugim menedżerem.

Chcę wyświetlić szczegóły pierwszego menedżera, aw następnych wierszach tylko te szczegóły dotyczące pracowników, które dotyczą podwładnego tego menedżera.

Przypuszczać

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

Chcę wyświetlić wyniki w taki sposób za pomocą wyrażeń CTE. Proszę powiedz mi, co zmodyfikować w moim sql, który podałem tutaj, aby wyciągnąć relacje menedżer-pracownik. Dzięki.

Chcę, aby wynik wyglądał następująco:

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

Czy to możliwe...?

Tomasz
źródło

Odpowiedzi:

210

Nie testowałem twojego kodu, po prostu starałem się pomóc ci zrozumieć, jak działa w komentarzach;

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

Najprostszym przykładem rekurencyjnego, CTEjaki przychodzi mi do głowy , aby zilustrować jego działanie, jest;

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

P 1) jak rośnie wartość N. jeśli wartość jest przypisać do N za każdym razem, po czym wartość N może być zwiększany, ale tylko pierwsza wartość N Czas był initialize .

A1:W tym przypadku Nnie jest zmienną. Njest aliasem. Jest to odpowiednik SELECT 1 AS N. Jest to składnia osobistych preferencji. Istnieją 2 główne metody tworzenia aliasów kolumn w pliku CTEin T-SQL. Podaję analog prostego CTEw Excelcelu wypróbowania i zilustrować w bardziej znany sposób co się dzieje.

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

Excel_CTE

Pytanie 2) teraz tutaj o CTE i rekurencji relacji pracowników w momencie, gdy dodam dwóch menedżerów i kilku dodatkowych pracowników pod drugim menedżerem, a problem zacznie się. Chcę wyświetlić szczegóły pierwszego menedżera, aw następnych wierszach pojawią się tylko te dane pracowników, którzy są podwładnymi tego menedżera

A2:

Czy ten kod odpowiada na Twoje pytanie?

--------------------------------------------
-- 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

Kolejny sql ze strukturą drzewa

SELECT ID,space(nLevel+
                    (CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
                )+Name
FROM Hierarchy
ORDER BY Family, nLevel
MarkD
źródło
zapytanie rekurencyjne CTE nie zwraca wyniku tak, jak chcę. chcę wyświetlić imię i nazwisko pierwszego menedżera, a następnie ponownie wyświetlić wszystkich jego podwładnych, wyświetlić imię drugiego menedżera, a następnie wyświetlić wszystkich jego podwładnych. Chcę, żeby wyjście było w ten sposób. jeśli to możliwe, zaktualizuj swoje zapytanie. dzięki
Thomas
Dodano kolumnę [Rodzina]. Sprawdź teraz.
MarkD
tutaj daję dane wyjściowe sposób, w jaki chcę wyświetlić wynik. proszę sprawdź i powiedz mi, czy to możliwe ... jeśli tak, zrób niezbędną modyfikację w ur sql. dzięki za twój wysiłek.
Thomas
Dlaczego jest ';' przed instrukcją WITH? "; WITH" Dzięki
Drewdin
2
@ SiKni8 - link wydaje się być martwy
MarkD
11

Chciałbym nakreślić krótką paralelę semantyczną do już poprawnej odpowiedzi.

Mówiąc „prosto”, rekurencyjne CTE można zdefiniować semantycznie jako następujące części:

1: Zapytanie CTE. Znany również jako ANCHOR.

2: Rekurencyjne zapytanie CTE na CTE w (1) z UNION ALL (lub UNION lub EXCEPT lub INTERSECT), więc ostateczny wynik jest odpowiednio zwracany.

3: Warunek narożnika / zakończenia. Co jest domyślnie, gdy nie ma więcej wierszy / krotek zwracanych przez zapytanie rekurencyjne.

Krótki przykład, który wyjaśni obraz:

;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

Wyjaśnienie: Pierwsze zapytanie CTE zwraca dostawców podstawowych (takich jak liście), którzy nie zaopatrują bezpośrednio żadnego innego dostawcy (-1)

Rekurencyjne zapytanie w pierwszej iteracji pobiera wszystkich dostawców, którzy zaopatrują dostawców, zwracanych przez ANCHOR. Ten proces trwa do momentu, gdy warunek zwróci krotki.

UNION ALL zwraca wszystkie krotki z wszystkich wywołań rekurencyjnych.

Kolejny dobry przykład można znaleźć tutaj .

PS: Aby rekurencyjne CTE działało, relacje muszą mieć hierarchiczny (rekurencyjny) warunek do pracy. Np .: elementId = elementParentId .. masz rację.

Vaibhav
źródło
9

Proces wykonywania jest naprawdę mylący z rekurencyjnym CTE, najlepszą odpowiedź znalazłem na https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx i streszczenie procesu wykonywania CTE jest jak poniżej.

Semantyka wykonywania rekurencyjnego jest następująca:

  1. Podziel wyrażenie CTE na elementy kotwiące i rekurencyjne.
  2. Uruchom zakotwiczone elementy członkowskie, tworząc pierwsze wywołanie lub podstawowy zestaw wyników (T0).
  3. Uruchom cykliczne elementy członkowskie z Ti jako danymi wejściowymi i Ti + 1 jako danymi wyjściowymi.
  4. Powtarzaj krok 3, aż zostanie zwrócony pusty zestaw.
  5. Zwróć zestaw wyników. To jest UNION ALL od T0 do Tn.
Pavan
źródło
-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
Vishal Motwani
źródło
1
Jest to odpowiedź zawierająca tylko kod, która nawet nie odpowiada na pytanie, ponieważ nie ma w niej rekurencyjnego CTE.
Dragomok