web-dev-qa-db-fra.com

Le moyen le plus simple de faire une auto-jointure récursive?

Quel est le moyen le plus simple de faire une auto-jointure récursive dans SQL Server? J'ai une table comme celle-ci:

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

Et je veux pouvoir obtenir les enregistrements uniquement liés à une hiérarchie commençant par une personne spécifique. Donc, si je demandais la hiérarchie de CJ par PersonID = 1, j'obtiendrais:

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

Et pour les EB, j'aurais:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Je suis un peu coincé là-dessus. Je ne vois pas comment le faire, à part une réponse à profondeur fixe basée sur un ensemble de jointures. Cela ferait ce qu'il se passe car nous n'aurons pas beaucoup de niveaux mais j'aimerais le faire correctement.

Merci! Chris.

87
Chris
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 modifiant la condition ORDER BY, vous pouvez modifier l’ordre des frères et sœurs.

91
Quassnoi

En utilisant les CTE, 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
20
Adriaan Stander

La requête Quassnoi avec une modification pour la grande table. Parents avec plus d'enfants que 10: Formater comme str (5) le row_number ()

 AVEC 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 DE m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN 
 FROM # t m 
 JOIN q 
 ON m.parentID = q.DBID 
) 
 SELECT * 
 FROM q 
 COMMANDER PAR 
 avant JC

4
guille

SQL 2005 ou version ultérieure, les CTE sont le moyen standard d’utiliser les exemples présentés.

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

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 personID=@PersonID

    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

(ce qui fonctionnera en 2005, ce n’est tout simplement pas la façon habituelle de le faire. Cela dit, si vous trouvez que la façon la plus facile de travailler est de fonctionner avec elle)

Si vous avez vraiment besoin de faire cela dans SQL7, vous pouvez effectuer à peu près ce qui précède dans un sproc mais vous ne pouvez pas en sélectionner un - SQL7 ne prend pas en charge les fichiers UDF.

2
eftpotrm

Vérifiez les éléments suivants pour vous aider à comprendre le concept de récursion CTE

DECLARE
@startDate DATETIME,
@endDate DATETIME

SET @startDate = '11/10/2011'
SET @endDate = '03/25/2012'

; WITH CTE AS (
    SELECT
        YEAR(@startDate) AS 'yr',
        MONTH(@startDate) AS 'mm',
        DATENAME(mm, @startDate) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        @startDate 'new_date'
    UNION ALL
    SELECT
        YEAR(new_date) AS 'yr',
        MONTH(new_date) AS 'mm',
        DATENAME(mm, new_date) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        DATEADD(d,1,new_date) 'new_date'
    FROM CTE
    WHERE new_date < @endDate
    )
SELECT yr AS 'Year', mon AS 'Month', count(dd) AS 'Days'
FROM CTE
GROUP BY mon, yr, mm
ORDER BY yr, mm
OPTION (MAXRECURSION 1000)
0
Premchandra Singh