再帰的な自己結合を行う最も簡単な方法は?


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 2009年

2
再帰クエリに関するSOの質問:stackoverflow.com/search
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
ハインツィ2009年

SOに新しい質問を投稿しました。stackoverflow.com
Kishore Kumar

@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)としてフォーマットする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.