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

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

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.


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.

-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
Esta é uma resposta somente de código que nem mesmo responde à pergunta, já que não há CTE recursiva nela.
— Dragomok de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.