재귀 적 셀프 조인을 수행하는 가장 간단한 방법은 무엇입니까?


100

SQL Server에서 재귀 적 자체 조인을 수행하는 가장 간단한 방법은 무엇입니까? 다음과 같은 테이블이 있습니다.

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

그리고 특정 사람으로 시작하는 계층 구조와 관련된 레코드 만 얻을 수 있기를 원합니다. 따라서 PersonID = 1로 CJ의 계층 구조를 요청하면 다음을 얻습니다.

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

그리고 EB의 경우 다음을 얻습니다.

PersonID | Initials | ParentID
2          EB         1
4          SW         2

많은 조인을 기반으로 한 고정 깊이 응답을 제외하고는 이것을 어떻게 할 수 있는지 생각할 수 없다는 것에 약간 붙어 있습니다. 레벨이 많지 않기 때문에 그렇게 되겠지만 제대로하고 싶습니다.

감사! 크리스.


2
어떤 버전의 SQL Server를 사용하고 있습니까? 즉, SQL 2000, 2005, 2008?
boydc7 09

2
재귀 쿼리에 관한 질문 : stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

답변:


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

주문 조건을 추가하여 트리 순서를 유지할 수 있습니다.

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

변화에 의해 ORDER BY조건을 당신은 형제의 순서를 변경할 수 있습니다.


7
+1, Chris PersonID = theIdYouAreLookingFor대신 ParentID IS NULL.
Heinzi


@Aaroninus : 상위 노드는 WITH절의 최상위 (앵커) 쿼리에 의해 정의됩니다 . 세부 사항이 필요하면 sqlfiddle.com 에서 바이올린을 만들고 여기에 링크를 게시하십시오.
Quassnoi 2014

24

CTE를 사용하면 이렇게 할 수 있습니다.

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
중요한 행운을 완전한 답변 WHERE PersonID = @PersonID
OLI B

5

큰 테이블에 대한 변경 사항이있는 Quassnoi 쿼리입니다. 10보다 많은 자녀가있는 부모 : str (5) the row_number () 형식 지정

q AS와 함께 
        (
        SELECT m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        #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
        #tm에서
        Q 가입
        ON m.parentID = q.DBID
        )
고르다 *
q에서
주문
        기원전


2

SQL 2005 이상에서는 CTE가 표시된 예에 따라 표준 방식으로 사용됩니다.

SQL 2000, 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

(2005 년에 작동 할 것입니다. 이는 표준 방식이 아닙니다. 즉, 작업하기 더 쉬운 방법을 찾으면 함께 실행하십시오)

SQL7에서이 작업을 수행해야하는 경우 sproc에서 대략 위의 작업을 수행 할 수 있지만 선택할 수 없습니다. SQL7은 UDF를 지원하지 않습니다.


2

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)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.