2009-11-18 14 views
83

Quelle est la manière la plus simple d'effectuer une auto-jointure récursive dans SQL Server? J'ai une table comme ceci:Façon la plus simple de faire une auto-jointure récursive?

PersonID | Initials | ParentID 
1   CJ   NULL 
2   EB   1 
3   MB   1 
4   SW   2 
5   YT   NULL 
6   IS   5 

Et je veux être en mesure d'obtenir les dossiers uniquement liés à une hiérarchie commençant par une personne spécifique. Donc, si j'ai demandé la hiérarchie de CJ par PersonID = 1 je recevrais:

PersonID | Initials | ParentID 
1   CJ   NULL 
2   EB   1 
3   MB   1 
4   SW   2 

Et pour obtiendriez de EB I:

PersonID | Initials | ParentID 
2   EB   1 
4   SW   2 

Je suis un peu coincé sur ce qui peut ne peut pas penser comment pour le faire en dehors d'une réponse à profondeur fixe basée sur un tas de jointures. Cela ferait comme il arrive parce que nous n'aurons pas beaucoup de niveaux mais je voudrais le faire correctement.

Merci! Chris.

+2

Quelle version de SQL Server utilisez-vous? c'est-à-dire Sql 2000, 2005, 2008? – chadhoc

+2

SO questions concernant les requêtes récursives: http://stackoverflow.com/search?q=sql-server+recursive –

Répondre

85
WITH q AS 
     (
     SELECT * 
     FROM mytable 
     WHERE ParentID IS NULL -- this condition defines the ultimate ancestors in your chain, change it as appropriate 
     UNION ALL 
     SELECT m.* 
     FROM mytable m 
     JOIN q 
     ON  m.parentID = q.PersonID 
     ) 
SELECT * 
FROM q 

En ajoutant la condition de commande, vous pouvez préserver l'ordre de l'arbre:

WITH q AS 
     (
     SELECT m.*, CAST(ROW_NUMBER() OVER (ORDER BY m.PersonId) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc 
     FROM mytable m 
     WHERE ParentID IS NULL 
     UNION ALL 
     SELECT m.*, q.bc + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.PersonID) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN 
     FROM mytable m 
     JOIN q 
     ON  m.parentID = q.PersonID 
     ) 
SELECT * 
FROM q 
ORDER BY 
     bc 

En changeant la condition ORDER BY vous pouvez modifier l'ordre des frères et sœurs.

+6

+1, sauf que Chris aurait besoin de 'PersonID = theIdYouAreLookingFor' au lieu de' ParentID IS NULL'. – Heinzi

+0

Est-il possible d'obtenir le même résultat en utilisant EntityFramework –

+1

@TheIndianProgrammmer: il est possible d'obtenir le même résultat sans utiliser EntityFramework. – Quassnoi

18

Utilisation CTEs vous pouvez le faire de cette façon

DECLARE @Table TABLE(
     PersonID INT, 
     Initials VARCHAR(20), 
     ParentID INT 
) 

INSERT INTO @Table SELECT  1,'CJ',NULL 
INSERT INTO @Table SELECT  2,'EB',1 
INSERT INTO @Table SELECT  3,'MB',1 
INSERT INTO @Table SELECT  4,'SW',2 
INSERT INTO @Table SELECT  5,'YT',NULL 
INSERT INTO @Table SELECT  6,'IS',5 

DECLARE @PersonID INT 

SELECT @PersonID = 1 

;WITH Selects AS (
     SELECT * 
     FROM @Table 
     WHERE PersonID = @PersonID 
     UNION ALL 
     SELECT t.* 
     FROM @Table t INNER JOIN 
       Selects s ON t.ParentID = s.PersonID 
) 
SELECT * 
FROm Selects 
+0

Bonne réponse complète avec l'important O 0 \t PersonID = @PersonID –

2

SQL 2005 ou plus tard, sont CTEs le moyen standard pour aller selon les exemples donnés.

SQL 2000, vous pouvez le faire en utilisant des FDU -

CREATE FUNCTION udfPersonAndChildren 
(
    @PersonID int 
) 
RETURNS @t TABLE (personid int, initials nchar(10), parentid int null) 
AS 
begin 
    insert into @t 
    select * from people p  
    where [email protected] 

    while @@rowcount > 0 
    begin 
     insert into @t 
     select p.* 
     from people p 
     inner join @t o on p.parentid=o.personid 
     left join @t o2 on p.personid=o2.personid 
     where o2.personid is null 
    end 

    return 
end 

(qui travaillera en 2005, il est tout simplement pas la manière standard de le faire Cela dit, si vous trouvez que la façon plus facile de travailler. , exécutez-le avec)

Si vous avez vraiment besoin de faire cela dans SQL7, vous pouvez faire à peu près ce qui est indiqué ci-dessus dans un sproc mais vous ne pouvez pas le sélectionner - SQL7 ne prend pas en charge les fonctions UDF.

4

La requête Quassnoi avec un changement pour une grande table. Les parents avec plus d'enfants alors 10: Formant comme str (5) le row_number()

 
WITH q AS 
     (
     SELECT m.*, CAST(str(ROW_NUMBER() OVER (ORDER BY m.ordernum),5) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc 
     FROM #t m 
     WHERE ParentID =0 
     UNION ALL 
     SELECT m.*, q.bc + '.' + str(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.ordernum),5) COLLATE Latin1_General_BIN 
     FROM #t m 
     JOIN q 
     ON  m.parentID = q.DBID 
     ) 
SELECT * 
FROM q 
ORDER BY 
     bc