Il modo più semplice per eseguire un auto-join ricorsivo?


100

Qual è il modo più semplice per eseguire un auto join ricorsivo in SQL Server? Ho un tavolo come questo:

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

E voglio essere in grado di ottenere i record solo relativi a una gerarchia che inizia con una persona specifica. Quindi, se richiedessi la gerarchia di CJ per PersonID = 1, otterrei:

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

E per gli EB otterrei:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Sono un po 'bloccato su questo non riesco a pensare a come farlo a parte una risposta a profondità fissa basata su un mucchio di join. Questo farebbe come succede perché non avremo molti livelli ma vorrei farlo correttamente.

Grazie! Chris.


2
Quale versione di SQL Server stai utilizzando? cioè Sql 2000, 2005, 2008?
boydc7

2
Domande SO riguardanti le query ricorsive: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

Risposte:


112
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

Aggiungendo la condizione di ordinazione, è possibile mantenere l'ordine della struttura ad albero:

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

Modificando la ORDER BYcondizione è possibile modificare l'ordine dei fratelli.


7
+1, tranne per il fatto che Chris avrebbe bisogno PersonID = theIdYouAreLookingForinvece di ParentID IS NULL.
Heinzi

Ho pubblicato una nuova domanda su SO, stackoverflow.com/questions/13535003/…
Kishore Kumar

@Aaroninus: il nodo genitore è definito dalla query più in alto (ancora) nella WITHclausola. Se hai bisogno di informazioni specifiche, crea un violino su sqlfiddle.com e pubblica il link qui.
Quassnoi

24

Usando CTE puoi farlo in questo modo

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

2
Buona risposta completa con l'importante WHERE PersonID = @PersonID
Oli B

5

La query Quassnoi con una modifica per la tabella di grandi dimensioni. Genitori con più figli di 10: formattare come str (5) il row_number ()

CON q AS 
        (
        SELEZIONA m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        DA #tm
        WHERE ParentID = 0
        UNION ALL
        SELEZIONA m. *, Q.bc + '.' + str (ROW_NUMBER () OVER (PARTITION BY m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN
        DA #tm
        ISCRIVITI q
        ON m.parentID = q.DBID
        )
SELEZIONARE *
DA q
ORDINATO DA
        avanti Cristo


2

SQL 2005 o versioni successive, le CTE sono il modo standard di procedere secondo gli esempi mostrati.

SQL 2000, puoi farlo usando le 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

(che funzionerà nel 2005, semplicemente non è il modo standard di farlo. Detto questo, se trovi che è il modo più semplice di lavorare, corri con esso)

Se hai davvero bisogno di farlo in SQL7, puoi fare più o meno quanto sopra in uno sproc ma non puoi selezionare da esso: SQL7 non supporta le UDF.


2

Controllare quanto segue per aiutare a comprendere il concetto di ricorsione 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.