Cara termudah untuk melakukan self-join rekursif?

100

Apa cara paling sederhana untuk melakukan self-join rekursif di SQL Server? Saya punya meja seperti ini:

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

Dan saya ingin mendapatkan catatan yang hanya terkait dengan hierarki yang dimulai dengan orang tertentu. Jadi Jika saya meminta hierarki CJ oleh PersonID = 1 saya akan mendapatkan:

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

Dan untuk EB, saya akan mendapatkan:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Saya agak terjebak dalam hal ini, saya tidak bisa memikirkan bagaimana melakukannya selain dari respons kedalaman tetap berdasarkan sekelompok gabungan. Ini akan terjadi karena kami tidak akan memiliki banyak level tetapi saya ingin melakukannya dengan benar.

Terima kasih! Chris.

Chris
sumber
2
Versi SQL Server mana yang Anda gunakan? yaitu Sql 2000, 2005, 2008?
boydc7
2
Pertanyaan SO tentang kueri rekursif: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

Jawaban:

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

Dengan menambahkan kondisi pemesanan, Anda dapat mempertahankan urutan pohon:

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

Dengan mengubah ORDER BYkondisi, Anda dapat mengubah urutan saudara kandung.

Quassnoi
sumber
7
+1, kecuali bahwa Chris akan membutuhkan, PersonID = theIdYouAreLookingForbukan ParentID IS NULL.
Heinzi
Saya telah memposting pertanyaan baru di SO, stackoverflow.com/questions/13535003/…
Kishore Kumar
@ Aaroninus: Node induk ditentukan oleh kueri (jangkar) paling atas dalam WITHklausa. Jika Anda membutuhkan informasi spesifik, silakan buat biola di sqlfiddle.com dan posting tautannya di sini.
Quassnoi
24

Dengan menggunakan CTE, Anda dapat melakukannya dengan cara ini

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
sumber
2
Jawaban lengkap yang bagus dengan WHERE PersonID = @PersonID
Oli B
5

Kueri Quassnoi dengan perubahan untuk tabel besar. Orang tua dengan lebih banyak anak daripada 10: Membentuk sebagai str (5) the row_number ()

DENGAN q AS 
        (
        PILIH m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        DARI #tm
        DI MANA ParentID = 0
        UNI SEMUA
        PILIH m. *, Q.bc + '.' + str (ROW_NUMBER () LEBIH DARI (PARTISI OLEH m.ParentID ORDER OLEH m.ordernum), 5) COLLATE Latin1_General_BIN
        DARI #tm
        BERGABUNG q
        ON m.parentID = q.DBID
        )
PILIH *
DARI q
DIPESAN OLEH
        bc

guille
sumber
2

SQL 2005 atau yang lebih baru, CTE adalah cara standar untuk digunakan sesuai contoh yang ditampilkan.

SQL 2000, Anda dapat melakukannya menggunakan 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

(yang akan berhasil pada tahun 2005, ini bukan cara standar untuk melakukannya. Meskipun demikian, jika Anda menemukan cara yang lebih mudah untuk bekerja, jalankan dengan itu)

Jika Anda benar-benar perlu melakukan ini di SQL7, Anda dapat melakukan secara kasar hal di atas dalam sproc tetapi tidak dapat memilih darinya - SQL7 tidak mendukung UDF.

eftpotrm.dll
sumber
2

Periksa berikut untuk membantu memahami konsep rekursi 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)
Premchandra Singh
sumber