web-dev-qa-db-fra.com

CTE récursif pour trouver Total pour tous les enfants

Voici un arbre d'assemblage que je souhaite rechercher en utilisant une requête récursive T-SQL (Vraisemblablement CTE) avec les résultats attendus ci-dessous. Je veux connaître le montant total par assemblée, quelle que soit la pièce.

Ce qui signifie que si je recherche "Rivet", je veux connaître le nombre total à chaque niveau de l'Assemblée, pas seulement le nombre direct d'enfants.

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

Actuellement, je peux obtenir les parents directs, mais je veux savoir comment étendre mon CTE pour me permettre de rouler ces informations vers le haut.

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 création

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)
16
markokstate

Ce CTE récursif ( SQL Fiddle ) devrait fonctionner avec votre exemple:

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
;

Production

ParentID    Count
1           6
2           3
3           2
4           1

Remarque: Vous avez mentionné dans les commentaires que la question ne contient qu'un exemple de table simplifiée et que les données réelles ont des index appropriés et gèrent correctement les doublons et les données.

Données utilisées ( 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)
;
14
Julien Vavasseur

Je ne suis pas sûr de comprendre ce que vous entendez par "montant" et d'où viennent les tables (?) PartInstances et les colonnes id et count in votre échantillon, mais j'ai calculé ce que je suppose à partir de vos données d'échantillon.

;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

J'espère que cela vous donnera quelques idées.

Mise à jour

Je comprends qu'il s'agit d'un exemple de test, mais vos données cassent tout à partir de 1NF. Votre table devrait très probablement être cassée en deux et normalisée.

0
Alex Kudryashev