2017-07-28 1 views
0

J'ai un arbre, où un nœud spécifique dans l'arborescence peut apparaître dans un autre nœud de l'arborescence. (2 dans mon exemple):SQL Server ignore les relations en double (enfant parent) dans la récursivité

   1 
      /  \ 
     2    3 
    / \     \ 
    4  5     6 
            \ 
            2 
           / \ 
           4  5 

L'avis 2 est dupliqué. D'abord sous 1 et deuxième sous 6. Mon récursion est:

with cte (ParentId, ChildId, Field1, Field2) AS (
    select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 
    from BillOfMaterials BOM 
    WHERE ParentId=x 

    UNION ALL 

    SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM 
    JOIN cte on BOM.ParentId = cte.ChildId 
) 
select * from cte; 

Mais le problème est que dans le résultat par rapport 2-4 et 2-5 est dupliquée (première de la relation 1-2 et seconde de la relation 6 -2):

ParentId ChildId     OtherFields 
    1   2 
    1   3 
    2   4 /*from 1-2*/ 
    2   5 /*from 1-2*/ 
    3   6 
    6   2 
    2   4 /*from 6-2*/ 
    2   5 /*from 6-2*/ 

Est-il possible d'ignorer les relations dupliquées? Je ne vois pas de logique pourquoi la récursivité doit-elle être exécutée sur les lignes qui sont déjà dans le résultat. Ce serait plus rapide. Quelque chose comme ça:

 with cte (ParentId, ChildId, Field1, Field2) AS (
      select BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 
      from BillOfMaterials BOM 
      WHERE ParentId=x 

      UNION ALL 

      SELECT BOM.ParentId, BOM.ChildId, BOM.Field1, BOM.Field2 FROM BillOfMaterials BOM 
      JOIN cte on BOM.ParentId = cte.ChildId 
------>  WHERE (select count(*) FROM SoFarCollectedResult WHERE ParentId=BOM.ParentId AND ChildId=BOM.ChildId) = 0 
     ) 
     select * from cte; 

J'ai trouvé this thread, mais il est de 8 ans.
J'utilise SQL Server 2016.

Si ce n'est pas possible, alors ma question est de savoir comment puis-je supprimer les doublons du résultat final, mais vérifiez distinct seulement sur les colonnes ParentId et ChildId?

Modifié:

résultat attendu est:

ParentId ChildId     OtherFields 
    1   2 
    1   3 
    2   4 
    2   5 
    3   6 
    6   2 
+0

Comment différenciez-vous 2 enfants de 1 et 2 enfants de 6? –

+0

Vos données sont erronées. Vous ne pouvez pas mettre ça ensemble comme ça. Comment savez-vous qu'un ensemble de 2,4 appartient à moins de 1 et l'autre à moins de 6 ans? Si vous avez besoin de ce type de relation, vous devez reconstruire votre structure de données car cela ne fonctionnera pas en tant que parent standard. –

+0

Il y a un problème fondamental ici. Un arbre peut être traversé selon différentes méthodologies. Il n'y a pas de véritable sens du «premier» - qui est de dire quelle occurrence d'un nœud mérite les enfants. Les nœuds ne peuvent pas être «instanciés», puis distingués par leur ascendance, ce qui fait qu'ils ne sont plus des nœuds. – Greenspark

Répondre

0

Modifier votre dernière requête à partir de:

select * from cte; 

Pour:

select * from cte group by ParentId, ChildId; 

Cela prendra essentiellement ce que vous avez en ce moment, mais aller un peu plus loin et supprimer les lignes qui sont déjà apparues, ce qui prendrait soin de votre problème en double. Assurez-vous que tous les * retournent ici sont ParentId et ChildId, s'il retourne d'autres colonnes, vous devrez les ajouter au GROUP BY ou lui appliquer une sorte d'agrégateur afin qu'il puisse encore grouper (max, min, count. ..).

Si vous avez plus de lignes que vous ne pouvez pas agréger ou de groupe, vous pouvez écrire la requête en tant que tel:

select * from cte where ID in (select MAX(ID) from cte group by ParentId, ChildId); 

ID serait votre ID de table primaire pour cte. Cela prendrait l'ID maximum lorsque les lignes correspondent, ce qui serait normalement votre dernière entrée, si vous voulez que l'entrée la plus ancienne change simplement MAX() en MIN().

0

Vous pouvez, en ajoutant à 2 petits trucs au SQL.

Mais vous avez besoin d'une colonne Id supplémentaire avec un numéro séquentiel.
Par exemple via une identité ou un champ datetime qui indique quand l'enregistrement a été inséré.
Pour la simple raison qu'en ce qui concerne la base de données, il n'y a pas d'ordre dans les enregistrements tels qu'ils ont été insérés, sauf si vous avez une colonne qui indique cet ordre.

Astuce 1) Joignez l'enregistrement CTE uniquement aux ID supérieurs. Parce que s'ils étaient inférieurs, ce sont les doublons que vous ne voulez pas rejoindre.

Trick 2) Utilisez la fonction de fenêtre ROW_NUMBER pour obtenir seulement ceux qui sont les plus proches de l'Id la récursion a commencé à partir

Exemple:

declare @BillOfMaterials table (Id int identity(1,1) primary key, ParentId int, ChildId int, Field1 varchar(8), Field2 varchar(8)); 

insert into @BillOfMaterials (ParentId, ChildId, Field1, Field2) values 
(1,2,'A','1-2'), 
(1,3,'B','1-3'), 
(2,4,'C','2-4'), -- from 1-2 
(2,5,'D','2-5'), -- from 1-2 
(3,6,'E','3-6'), 
(6,2,'F','6-2'), 
(2,4,'G','2-4'), -- from 6-2 
(2,5,'H','2-5'); -- from 6-2 

;with cte AS 
(
    select Id as BaseId, 0 as Level, BOM.* 
    from @BillOfMaterials BOM 
    WHERE ParentId in (1) 

    UNION ALL 

    SELECT CTE.BaseId, CTE.Level + 1, BOM.* 
    FROM cte 
    JOIN @BillOfMaterials BOM on (BOM.ParentId = cte.ChildId and BOM.Id > CTE.Id) 
) 
select ParentId, ChildId, Field1, Field2 
from (
    select * 
    --, row_number() over (partition by BaseId, ParentId, ChildId order by Id) as RNbase 
    , row_number() over (partition by ParentId, ChildId order by Id) as RN 
    from cte 
) q 
where RN = 1 
order by ParentId, ChildId; 

Résultat:

ParentId ChildId Field1 Field2 
-------- ------- ------ ------ 
1  2  A  1-2 
1  3  B  1-3 
2  4  C  2-4 
2  5  D  2-5 
3  6  E  3-6 
6  2  F  6-2 

Quoi qu'il en soit, en tant que sidenote, normalement une table de relation parent-enfant est utilisée différemment.
Le plus souvent, il s'agit simplement d'une table avec des combinaisons uniques parent-enfant qui sont des clés étrangères à une autre table où cet ID est une clé primaire. Alors que les autres champs sont conservés dans cette autre table.

+0

Mais dans ce cas, 2 - 4 et 2 - 5 sont également en double. S'il y a, par exemple, 10 niveaux sous le nœud 2, la récursivité doit descendre tous les 10 niveaux deux fois. Et je veux éviter cela si possible. – Makla

+0

@Makla Pourriez-vous ajouter le résultat attendu à votre question? Comme vous l'exprimez maintenant, il n'y aurait que le 2-4 dans le résultat, et aucun 2-5 n'importe où. – LukStorms