CTE do SQL Server e exemplo de recursão

109

Nunca uso CTE com recursão. Eu estava lendo um artigo sobre isso. Este artigo mostra informações do funcionário com a ajuda do servidor Sql CTE e recursão. Basicamente, mostra as informações dos funcionários e de seus gerentes. Não consigo entender como essa consulta funciona. Aqui está a consulta:

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

Aqui estou postando sobre como a saída está sendo exibida: insira a descrição da imagem aqui

Só preciso saber como isso mostra o gerente primeiro e depois o subordinado em um loop. Acho que a primeira instrução sql dispara apenas uma vez e retorna todos os ids de funcionários.

E a segunda consulta dispara repetidamente, consultando o banco de dados no qual o funcionário existe com o ID de gerente atual.

Explique como a instrução sql é executada em um loop interno e também me diga a ordem de execução do sql. Obrigado.

MINHA 2ª fase de questão

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

Q 1) como o valor de N está sendo incrementado? se o valor for atribuído a N todas as vezes, o valor N pode ser incrementado, mas apenas na primeira vez que o valor N foi inicializado.

Q 2) CTE e recursão das relações com os funcionários:

No momento em que acrescento dois gerentes e mais alguns funcionários sob o segundo gerente, é onde o problema começa.

Desejo exibir o primeiro detalhe do gerente e nas próximas linhas apenas os detalhes do funcionário que se relacionam ao subordinado desse gerente.

Suponha

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

Desejo exibir os resultados dessa forma com expressões CTE. Diga-me o que devo modificar em meu sql que forneci aqui para obter as relações gerente-funcionário. Obrigado.

Quero que a saída seja assim:

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

Isso é possível...?

Thomas
fonte

Respostas:

210

Não testei seu código, apenas tentei ajudá-lo a entender como ele funciona em comentários;

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

O exemplo mais simples de um recursivo CTEque posso pensar para ilustrar sua operação é;

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

Q 1) como o valor de N está sendo incrementado. se o valor for atribuído a N todas as vezes, o valor N pode ser incrementado, mas apenas na primeira vez que o valor N foi inicializado .

A1:Nesse caso, Nnão é uma variável. Né um pseudônimo. É o equivalente a SELECT 1 AS N. É uma sintaxe de preferência pessoal. Existem 2 principais métodos de aliasing colunas em um CTEno T-SQL. Incluí o análogo de um simples CTEem Excelpara tentar ilustrar de uma forma mais familiar o que está acontecendo.

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

Excel_CTE

Q 2) agora aqui sobre CTE e recursão da relação do funcionário no momento em que adiciono dois gerentes e adiciono mais alguns funcionários sob o segundo gerente, em seguida, o problema começa. Eu quero exibir os primeiros detalhes do gerente e nas próximas linhas apenas os detalhes dos funcionários virão aqueles que são subordinados desse gerente

A2:

Este código responde à sua pergunta?

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

Outro sql com estrutura em árvore

SELECT ID,space(nLevel+
                    (CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
                )+Name
FROM Hierarchy
ORDER BY Family, nLevel
MarkD
fonte
a consulta recursiva CTE não retorna o resultado da maneira que desejo. desejo exibir o nome do primeiro gerente e, em seguida, exibir todos os seus subordinados novamente exibir o nome do segundo gerente e, em seguida, exibir todos os seus subordinados. eu quero que a saída seja desta forma. se possível, atualize sua consulta. obrigado
Thomas
Adicionada coluna [Família]. Verifique agora.
MarkD
aqui eu forneço a saída da maneira que desejo exibir o resultado. por favor, verifique e me diga se é possível ... se sim, então faça as modificações necessárias no seu sql. Obrigado pelo seu esforço.
Thomas
porque é o ';' antes da instrução WITH? "; COM" Obrigado
Drewdin
2
@ SiKni8 - o link parece estar morto
MarkD
11

Gostaria de esboçar um breve paralelo semântico para uma resposta já correta.

Em termos 'simples', um CTE recursivo pode ser semanticamente definido como as seguintes partes:

1: A consulta CTE. Também conhecido como ANCHOR.

2: A consulta CTE recursiva na CTE em (1) com UNION ALL (ou UNION ou EXCEPT ou INTERSECT) para que o resultado final seja retornado de acordo.

3: A condição de canto / terminação. Que é por padrão quando não há mais linhas / tuplas retornadas pela consulta recursiva.

Um pequeno exemplo que tornará a imagem clara:

;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

Explicação: A primeira consulta CTE retorna os fornecedores de base (como folhas) que não fornecem para nenhum outro fornecedor diretamente (-1)

A consulta recursiva na primeira iteração obtém todos os fornecedores que fornecem aos fornecedores retornados pela ANCHOR. Este processo continua até que a condição retorne tuplas.

UNION ALL retorna todas as tuplas sobre o total de chamadas recursivas.

Outro bom exemplo pode ser encontrado aqui .

PS: Para que um CTE recursivo funcione, as relações devem ter uma condição hierárquica (recursiva) para funcionar. Ex: elementId = elementParentId .. você entendeu.

Vaibhav
fonte
9

O processo de execução é realmente confuso com CTE recursiva, encontrei a melhor resposta em https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx e o resumo do processo de execução de CTE é como abaixo.

A semântica da execução recursiva é a seguinte:

  1. Divida a expressão CTE em membros âncora e recursivos.
  2. Execute o (s) membro (s) âncora criando a primeira invocação ou conjunto de resultados base (T0).
  3. Execute o (s) membro (s) recursivo (s) com Ti como entrada e Ti + 1 como saída.
  4. Repita a etapa 3 até que um conjunto vazio seja retornado.
  5. Retorne o conjunto de resultados. Esta é uma UNION ALL de T0 a Tn.
Pavan
fonte
-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
fonte
1
Esta é uma resposta somente de código que nem mesmo responde à pergunta, já que não há CTE recursiva nela.
Dragomok de