CTE recursivo para encontrar o total para todas as crianças

16

Aqui está uma árvore de montagem que eu quero pesquisar usando uma T-SQLConsulta recursiva (presumivelmente CTE) com os resultados esperados abaixo. Quero saber o valor total por montagem, dada qualquer peça.

Ou seja, se eu procurar por 'Rebite', quero saber a contagem total em cada nível na montagem, não apenas a contagem direta de filhos.

Assembly (id:1)
    |
    |-Rivet
    |-Rivet
    |-SubAssembly (id:2)
    |   |
    |   |-Rivet
    |   |-Bolt
    |   |-Bolt
    |   |-SubSubAssembly (id:3)
    |      |
    |      |-Rivet
    |      |-Rivet
    |
    |-SubAssembly (id:4)
       |-Rivet
       |-Bolt

    DESIRED Results
    -------
    ID, Count
    1 , 6
    2 , 3
    3 , 2
    4 , 1

Atualmente, posso obter os pais diretos, mas quero saber como estender minha CTE para permitir que eu role essas informações para cima.

With DirectParents AS(
--initialization
Select InstanceID, ParentID
From Instances i 
Where i.Part = 'Rivet'

UNION ALL
--recursive execution
Select i.InstanceID, i.ParentID
From PartInstances i  INNER JOIN DirectParents p
on i.ParentID = p.InstanceID

)

select ParentID, Count(instanceid) as Totals
from DirectParents
group by InstanceID, ParentID

Results
-------
ID, Count
1 , 2
2 , 2
3 , 2
4 , 1

Script de criação

CREATE TABLE [dbo].[Instances] ( 
  [InstanceID] NVARCHAR (50) NOT NULL, 
  [Part] NVARCHAR (50) NOT NULL, 
  [ParentID] NVARCHAR (50) NOT NULL, );



INSERT INTO Instances 
Values 
  (1, 'Assembly', 0), 
  (50, 'Rivet', 1), 
  (50, 'Rivet', 1), 
  (2, 'SubAssembly', 1), 
  (50, 'Rivet', 2), 
  (51, 'Bolt', 2), 
  (51, 'Bolt', 2), 
  (3, 'SubSubAssembly', 2), 
  (50, 'Rivet', 3), 
  (50, 'Rivet', 3), 
  (4, 'SubAssembly2', 1), 
  (50, 'Rivet', 4), 
  (51, 'Bolt', 4)
markokstate
fonte

Respostas:

14

Este CTE recursivo ( SQL Fiddle ) deve funcionar com sua amostra:

WITH cte(ParentID) AS(
    SELECT ParentID FROM @Instances WHERE [Part] = 'Rivet'
    UNION ALL
    SELECT i.ParentID FROM cte c
    INNER JOIN @Instances i ON c.ParentID = i.InstanceID
    WHERE i.ParentID > 0
)
SELECT ParentID, count(*) 
FROM cte
GROUP BY ParentID
ORDER BY ParentID
;

Resultado

ParentID    Count
1           6
2           3
3           2
4           1

Nota: Você mencionou nos comentários que a pergunta contém apenas uma tabela de amostra simplificada e os dados reais têm índices adequados e manipulam duplicatas e dados adequadamente.

Dados usados ( SQL Fiddle ):

DECLARE @Instances TABLE( 
    [InstanceID] int NOT NULL
    , [Part] NVARCHAR (50) NOT NULL
    , [ParentID] int NOT NULL
);

INSERT INTO @Instances([InstanceID], [Part], [ParentID])
VALUES 
    (1, 'Assembly', 0)
    , (50, 'Rivet', 1)
    , (50, 'Rivet', 1)
    , (2, 'SubAssembly', 1)
    , (50, 'Rivet', 2)
    , (51, 'Bolt', 2)
    , (51, 'Bolt', 2)
    , (3, 'SubSubAssembly', 2)
    , (50, 'Rivet', 3)
    , (50, 'Rivet', 3)
    , (4, 'SubAssembly2', 1)
    , (50, 'Rivet', 4)
    , (51, 'Bolt', 4)
;
Julien Vavasseur
fonte
Ótima resposta obrigado! Existe uma solução fácil para fazer isso recursivamente para todas as instâncias [montagem, rebite etc.]?
greenhoorn 28/03/19
0

Não sei se entendi o que você quer dizer com "quantidade" e onde a tabela (?) PartInstances e o ID e a contagem de colunas vêm da sua amostra, mas calculei o que acho dos dados da amostra.

;with ins as (
select [InstanceID], [Part],[ParentID],0 lvl
from instances where ParentID=0
union all
select i.[InstanceID], i.[Part],i.[ParentID], lvl+1
from instances i 
inner join ins on i.parentid=ins.InstanceID
)
select InstanceID,part,COUNT(*) cnt
from ins
group by instanceid,part

Espero que isso lhe dê algumas idéias.

Atualizar

Entendo que este é um exemplo de teste, mas seus dados interrompem tudo a partir de 1NF. Provavelmente sua mesa deve ser dividida em duas e normalizada.

Alex Kudryashev
fonte
Veja os resultados na primeira seção do código. Esses são desejados. Ao procurar por 'Rebite', estou procurando obter um total de todos os rebites que receberem qualquer peça da árvore. Significando instanceID 1 deve me dar um resultado de 6. Significando que contém 6 rebites totais em sua estrutura de árvore completa.
Markokstate 19/05