Einfachste Möglichkeit, eine rekursive Selbstverbindung durchzuführen?

100

Was ist der einfachste Weg, um einen rekursiven Self-Join in SQL Server durchzuführen? Ich habe einen Tisch wie diesen:

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

Und ich möchte in der Lage sein, die Datensätze nur in Bezug auf eine Hierarchie ab einer bestimmten Person abzurufen. Wenn ich also die Hierarchie von CJ mit PersonID = 1 anfordern würde, würde ich Folgendes erhalten:

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

Und für EBs würde ich bekommen:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Ich bin ein bisschen festgefahren, kann mir nicht vorstellen, wie ich es machen soll, abgesehen von einer Antwort mit fester Tiefe, die auf einer Reihe von Joins basiert. Dies würde so tun, wie es passiert, weil wir nicht viele Level haben, aber ich würde es gerne richtig machen.

Vielen Dank! Chris.

Chris
quelle
2
Welche Version von SQL Server verwenden Sie? dh SQL 2000, 2005, 2008?
boydc7
2
SO Fragen zu rekursiven Abfragen: stackoverflow.com/search?q=sql-server+recursive
OMG Ponys

Antworten:

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

Durch Hinzufügen der Bestellbedingung können Sie die Baumreihenfolge beibehalten:

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

Durch Ändern der ORDER BYBedingung können Sie die Reihenfolge der Geschwister ändern.

Quassnoi
quelle
7
+1, außer dass Chris PersonID = theIdYouAreLookingForstattdessen brauchen würde ParentID IS NULL.
Heinzi
Ich habe eine neue Frage auf SO, stackoverflow.com/questions/13535003/…
Kishore Kumar
@Aaroninus: Der übergeordnete Knoten wird durch die oberste (Anker-) Abfrage in der WITHKlausel definiert. Wenn Sie Einzelheiten benötigen, erstellen Sie bitte eine Geige auf sqlfiddle.com und veröffentlichen Sie den Link hier.
Quassnoi
24

Mit CTEs können Sie dies auf diese Weise tun

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
Adriaan Stander
quelle
2
Gute vollständige Antwort mit der wichtigen WHERE PersonID = @PersonID
Oli B
5

Die Quassnoi-Abfrage mit einer Änderung für große Tabelle. Eltern mit mehr Kindern als 10: Formatieren der Zeilennummer () als str (5)

MIT q AS 
        (
        SELECT m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        VON #tm
        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
        VON #tm
        JOIN q
        ON m.parentID = q.DBID
        )
WÄHLEN *
VON q
SORTIEREN NACH
        bc

Guille
quelle
2

SQL 2005 oder höher sind CTEs gemäß den gezeigten Beispielen der Standard.

SQL 2000 können Sie dies mit UDFs tun -

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

(was 2005 funktionieren wird, ist einfach nicht die Standardmethode. Wenn Sie jedoch feststellen, dass dies die einfachere Arbeitsweise ist, führen Sie es aus.)

Wenn Sie dies in SQL7 wirklich tun müssen, können Sie ungefähr das oben Genannte in einem Sproc ausführen, konnten jedoch keine Auswahl treffen - SQL7 unterstützt keine UDFs.

eftpotrm
quelle
2

Überprüfen Sie Folgendes, um das Konzept der CTE-Rekursion besser zu verstehen

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)
Premchandra Singh
quelle