Cách đơn giản nhất để thực hiện tự tham gia đệ quy?


100

Cách đơn giản nhất để thực hiện tự tham gia đệ quy trong SQL Server là gì? Tôi có một bảng như thế này:

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

Và tôi muốn có thể lấy các bản ghi chỉ liên quan đến hệ thống phân cấp bắt đầu từ một người cụ thể. Vì vậy, nếu tôi yêu cầu phân cấp của CJ theo PersonID = 1, tôi sẽ nhận được:

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

Và đối với EB, tôi sẽ nhận được:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

Tôi hơi mắc kẹt về điều này, không thể nghĩ làm thế nào để làm điều đó ngoài một phản hồi có độ sâu cố định dựa trên một loạt các phép nối. Điều này sẽ làm như nó xảy ra bởi vì chúng tôi sẽ không có nhiều cấp độ nhưng tôi muốn làm đúng cách.

Cảm ơn! Chris.


2
Bạn đang sử dụng phiên bản SQL Server nào? tức là Sql 2000, 2005, 2008?
boydc7

2
SO câu hỏi liên quan đến truy vấn đệ quy: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

Câu trả lời:


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

Bằng cách thêm điều kiện đặt hàng, bạn có thể giữ nguyên đơn hàng cây:

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

Bằng cách thay đổi ORDER BYđiều kiện, bạn có thể thay đổi thứ tự của anh chị em.


7
+1, ngoại trừ Chris sẽ cần PersonID = theIdYouAreLookingForthay vì ParentID IS NULL.
Heinzi

Tôi đã đăng một câu hỏi mới trên SO, stackoverflow.com/questions/13535003/…
Kishore Kumar,

@Aaroninus: Nút cha được xác định bởi truy vấn trên cùng (neo) trong WITHmệnh đề. Nếu bạn cần chi tiết cụ thể, vui lòng tạo một fiddle trên sqlfiddle.com và đăng liên kết tại đây.
Quassnoi

24

Sử dụng CTE bạn có thể làm theo cách này

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
Câu trả lời hoàn toàn tốt với các quan trọng ĐÂU PersonID = @PersonID
Oli B

5

Truy vấn Quassnoi với một thay đổi cho bảng lớn. Cha mẹ có nhiều con hơn thì 10: Tạo thành str (5) row_number ()

VỚI q NHƯ 
        (
        CHỌN m. *, CAST (str (ROW_NUMBER () HẾT (ORDER BY m.ordernum), 5) NHƯ VARCHAR (TỐI ĐA)) COLLATE Tiếng Latinh1_General_BIN NHƯ bc
        TỪ #tm
        WHERE ParentID = 0
        ĐOÀN KẾT TẤT CẢ
        CHỌN m. *, Q.bc + '.' + str (ROW_NUMBER () HẾT (PARTITION BY m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN
        TỪ #tm
        THAM GIA q
        ON m.parentID = q.DBID
        )
LỰA CHỌN *
TỪ q
ĐẶT BỞI
        bc


2

SQL 2005 trở lên, CTE là cách chuẩn để sử dụng theo các ví dụ được hiển thị.

SQL 2000, bạn có thể làm điều đó bằng cách sử dụng 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

(sẽ hoạt động vào năm 2005, đó không phải là cách làm tiêu chuẩn. Điều đó nói rằng, nếu bạn thấy rằng cách dễ dàng hơn để làm việc, hãy chạy với nó)

Nếu bạn thực sự cần làm điều này trong SQL7, bạn có thể thực hiện gần như những điều trên trong một chương trình nhưng không thể chọn từ nó - SQL7 không hỗ trợ UDF.


2

Kiểm tra phần sau để giúp hiểu khái niệm về đệ quy 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.