Beispiel für SQL Server CTE und Rekursion

109

Ich benutze CTE nie mit Rekursion. Ich habe gerade einen Artikel darüber gelesen. Dieser Artikel zeigt Mitarbeiterinformationen mit Hilfe von SQL Server CTE und Rekursion. Grundsätzlich werden Mitarbeiter und deren Managerinformationen angezeigt. Ich kann nicht verstehen, wie diese Abfrage funktioniert. Hier ist die Abfrage:

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

Hier poste ich darüber, wie die Ausgabe angezeigt wird: Geben Sie hier die Bildbeschreibung ein

Ich muss nur wissen, wie es zuerst den Manager und dann seinen Untergebenen in einer Schleife zeigt. Ich denke, die erste SQL-Anweisung wird nur einmal ausgelöst und gibt alle Mitarbeiter-IDs zurück.

Die zweite Abfrage wird wiederholt ausgelöst und fragt die Datenbank ab, in der der Mitarbeiter mit der aktuellen Manager-ID vorhanden ist.

Bitte erläutern Sie, wie die SQL-Anweisung in einer internen Schleife ausgeführt wird, und teilen Sie mir auch die SQL-Ausführungsreihenfolge mit. Vielen Dank.

MEINE 2. Phase der Frage

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

F 1) Wie wird der Wert von N erhöht? Wenn der Wert jedes Mal N zugewiesen wird, kann der N-Wert erhöht werden, aber nur beim ersten Initialisieren des N-Werts.

F 2) CTE und Rekursion der Mitarbeiterbeziehungen:

In dem Moment, in dem ich zwei Manager und ein paar weitere Mitarbeiter unter dem zweiten Manager hinzufüge, beginnt das Problem.

Ich möchte das erste Managerdetail und in den nächsten Zeilen nur die Mitarbeiterdetails anzeigen, die sich auf den Untergebenen dieses Managers beziehen.

Annehmen

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

Ich möchte die Ergebnisse so mit CTE-Ausdrücken anzeigen. Bitte sagen Sie mir, was ich in meinem SQL ändern soll, das ich hier angegeben habe, um die Beziehungen zwischen Manager und Mitarbeiter zu verbessern. Vielen Dank.

Ich möchte, dass die Ausgabe folgendermaßen aussieht:

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

Ist das möglich...?

Thomas
quelle

Antworten:

210

Ich habe Ihren Code nicht getestet, sondern nur versucht, Ihnen zu helfen, die Funktionsweise von Kommentaren zu verstehen.

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

Das einfachste Beispiel für eine Rekursive, die CTEich mir vorstellen kann, um ihre Funktionsweise zu veranschaulichen, ist:

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

Q 1) wie der Wert von N erhöht wird. Wenn N jedes Mal ein Wert zugewiesen wird, kann der N-Wert erhöht werden, aber nur beim ersten Initialisieren des N-Werts .

A1:In diesem Fall Nist keine Variable. Nist ein Alias. Es ist das Äquivalent von SELECT 1 AS N. Es ist eine Syntax persönlicher Vorlieben. Es gibt zwei Hauptmethoden zum Aliasing von Spalten in einem CTEIn T-SQL. Ich habe das Analogon eines einfachen Beispiels CTEaufgenommen Excel, um auf eine vertraute Weise zu veranschaulichen, was passiert.

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

Excel_CTE

F 2) jetzt hier über CTE und Rekursion der Mitarbeiterbeziehung in dem Moment, in dem ich zwei Manager hinzufüge und einige weitere Mitarbeiter unter dem zweiten Manager hinzufüge, dann beginnt das Problem. Ich möchte die Details des ersten Managers anzeigen und in den nächsten Zeilen werden nur die Mitarbeiterdetails angezeigt, die diesem Manager untergeordnet sind

A2:

Beantwortet dieser Code Ihre Frage?

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

Ein weiteres SQL mit Baumstruktur

SELECT ID,space(nLevel+
                    (CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
                )+Name
FROM Hierarchy
ORDER BY Family, nLevel
MarkD
quelle
Die rekursive CTE-Abfrage gibt das Ergebnis nicht wie gewünscht zurück. Ich möchte den ersten Managernamen anzeigen und dann wieder alle seine Untergebenen anzeigen. Den zweiten Managernamen anzeigen und dann alle seine Untergebenen anzeigen. Ich möchte, dass die Ausgabe auf diese Weise erfolgen sollte. Wenn möglich, aktualisieren Sie bitte Ihre Anfrage. danke
Thomas
Spalte [Familie] hinzugefügt. Jetzt prüfen.
MarkD
Hier gebe ich der Ausgabe die Art und Weise, wie ich das Ergebnis anzeigen möchte. Bitte überprüfen Sie und sagen Sie mir, ob es möglich ist. Wenn ja, nehmen Sie die erforderlichen Änderungen in ur sql vor. Danke für deine Mühe.
Thomas
warum ist der ';' vor der WITH-Anweisung? "; WITH" Danke
Drewdin
2
@ SiKni8 - der Link scheint tot zu sein
MarkD
11

Ich möchte eine kurze semantische Parallele zu einer bereits korrekten Antwort skizzieren.

In 'einfachen' Begriffen kann ein rekursiver CTE semantisch als die folgenden Teile definiert werden:

1: Die CTE-Abfrage. Auch als ANKER bekannt.

2: Die rekursive CTE-Abfrage auf dem CTE in (1) mit UNION ALL (oder UNION oder EXCEPT oder INTERSECT), sodass das Endergebnis entsprechend zurückgegeben wird.

3: Die Eck- / Beendigungsbedingung. Dies ist standardmäßig der Fall, wenn von der rekursiven Abfrage keine Zeilen / Tupel mehr zurückgegeben werden.

Ein kurzes Beispiel, das das Bild klar macht:

;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

Erläuterung: Die erste CTE-Abfrage gibt die Basislieferanten (wie Blätter) zurück, die nicht direkt an einen anderen Lieferanten liefern (-1).

Die rekursive Abfrage in der ersten Iteration erhält alle Lieferanten, die an die vom ANCHOR zurückgegebenen Lieferanten liefern. Dieser Vorgang wird fortgesetzt, bis die Bedingung Tupel zurückgibt.

UNION ALL gibt alle Tupel über die gesamten rekursiven Aufrufe zurück.

Ein weiteres gutes Beispiel finden Sie hier .

PS: Damit ein rekursiver CTE funktioniert, müssen die Beziehungen eine hierarchische (rekursive) Bedingung haben, an der gearbeitet werden kann. Beispiel: elementId = elementParentId .. Sie erhalten den Punkt.

Vaibhav
quelle
8

Der Ausführungsprozess ist wirklich verwirrend mit rekursivem CTE. Die beste Antwort fand ich unter https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx und der Zusammenfassung des CTE-Ausführungsprozesses ist wie unten.

Die Semantik der rekursiven Ausführung ist wie folgt:

  1. Teilen Sie den CTE-Ausdruck in Anker- und rekursive Elemente auf.
  2. Führen Sie die Ankermitglieder aus, die den ersten Aufruf oder die erste Basisergebnismenge (T0) erstellen.
  3. Führen Sie die rekursiven Elemente mit Ti als Eingabe und Ti + 1 als Ausgabe aus.
  4. Wiederholen Sie Schritt 3, bis ein leerer Satz zurückgegeben wird.
  5. Geben Sie die Ergebnismenge zurück. Dies ist eine UNION ALL von T0 bis Tn.
Pavan
quelle
-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
quelle
1
Dies ist eine reine Code-Antwort, die die Frage nicht einmal beantwortet, da sie keinen rekursiven CTE enthält.
Dragomok