6

私は隣接リストによって記述された階層を持っています。必ずしも単一のルート要素である必要はありませんが、私は、ヒラリー内のリーフ(終端)項目を識別するデータを持っています。だからこのように見える階段...SQL階層 - 与えられたノードのすべての祖先の完全なパスを解決します。

1 
- 2 
- - 4 
- - - 7 
- 3 
- - 5 
- - 6 
8 
- 9 

...このような表で説明されます。 注::私はこのフォーマットを変更する能力がありません。ここ

id parentid isleaf 
--- -------- ------ 
1 null  0 
2 1  0 
3 1  0 
4 2  0 
5 3  1 
6 3  1 
7 4  1 
8 null  0 
9 8  1 

は、サンプルテーブル定義とデータである。このことから

CREATE TABLE [dbo].[HiearchyTest](
    [id] [int] NOT NULL, 
    [parentid] [int] NULL, 
    [isleaf] [bit] NOT NULL 
) 
GO 

INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (1, NULL, 0) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (2, 1, 0) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (3, 1, 0) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (4, 2, 0) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (5, 3, 1) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (6, 3, 1) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (7, 4, 1) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (8, NULL, 0) 
INSERT [dbo].[HiearchyTest] ([id], [parentid], [isleaf]) VALUES (9, 8, 1) 
GO 

、私は、任意のIDを提供し、それぞれのすべての子孫を含むすべての祖先のリストを取得する必要があります。

id descendentid 
-- ------------ 
1 1 
1 3 
1 6 
3 3 
3 6 
6 6 
  • ID 6ちょうどその親、ID 3は3と6
  • のdecendentsを持つことになり、それ自体
  • あります。私はID = 6の入力を提供するのであれば、私は次のように期待します
  • 親、ID 1は、私は、階層内の各レベルでロールアップ計算を提供するためにこのデータを使用する1のdecendents、3、6

を有するであろう。上記のデータセットを取得できるとすれば、これはうまくいきます。

私はこれを、2つの反発的な頭字語を使用して達成しました.1つは、検索で各ノードの「終端」項目を取得することです。次に、私が選択したノードの完全な祖先を取得する2番目のノード(つまり、6は6,3,1に解決されます)が立ち上がり、フルセットを取得します。私は何かが欠けていることを望んでおり、これは1ラウンドで達成することができます。ここでは、二重再帰コード例です。

declare @test int = 6; 

with cte as (

    -- leaf nodes 
    select id, parentid, id as terminalid 
    from HiearchyTest 
    where isleaf = 1 

    union all 

    -- walk up - preserve "terminal" item for all levels 
    select h.id, h.parentid, c.terminalid 
    from HiearchyTest as h 
    inner join 
    cte as c on h.id = c.parentid 

) 

, cte2 as (

    -- get all ancestors of our test value 
    select id, parentid, id as descendentid 
    from cte 
    where terminalid = @test 

    union all 

    -- and walkup from each to complete the set 
    select h.id, h.parentid, c.descendentid 
    from HiearchyTest h 
    inner join cte2 as c on h.id = c.parentid 

) 

-- final selection - order by is just for readability of this example 
select id, descendentid 
from cte2 
order by id, descendentid 

追加詳細:「本当の」階層は例よりもはるかに大きくなります。それは技術的に無限の深さを持つことができますが、現実的には10レベルを超えることはほとんどありません。

要約すると、階層的に2回繰り返すのではなく、1つの再帰的cteでこれを達成できるかどうかです。

+0

https://msdn.microsoft.com/en-us/library/bb677173.aspxここでは、正確にこれを対象としたいくつかの新しい階層機能に関するMicrosoftの記事があります。 – Matt

+0

@Matt - 提案をいただきありがとうございますが、上記の私の注意は、データ構造を変更する能力がないことを示しています(私のテーブルではありません)。だから、私はhierarchyid列を追加する能力がありません。さらに、階層IDに関する私の経験では、私が求めていることをする方法を見ていない。id = 6の入力に基づいて、このIDのすべての祖先の完全な子孫リストが必要であることがわかります。 –

+0

私はすぐに読んだ:)再帰的なcteは、私が再び読んで、私が助けることができるものかどうかを確認するルートです。 – Matt

答えて

1

これは私が質問を読んで以来、私は気になりました、そして、私はちょうど再び考え直すために戻ってきました.....とにかく、なぜあなたは、子孫のすべてを得るために、あなたは子孫ではない祖先を求めており、結果セットは他の兄弟、孫などを取得しようとしていません。この場合、親と祖父母を得ています。あなたの最初のcteは、祖先のidがparentidでもある場合を除いて、あなたが知る必要があるすべてを提供します。したがって、組合全体では、元の祖先をセットアップするための少しの魔法があり、2回目の再帰なしで必要なものはすべて用意されています。あなたの最初のcteから設定

declare @test int = 6; 

with cte as (

    -- leaf nodes 
    select id, parentid, id as terminalid 
    from HiearchyTest 
    where isleaf = 1 

    union all 

    -- walk up - preserve "terminal" item for all levels 
    select h.id, h.parentid, c.terminalid 
    from HiearchyTest as h 
    inner join 
    cte as c on h.id = c.parentid 

) 

, cteAncestors AS (

    SELECT DISTINCT 
     id = IIF(parentid IS NULL, @Test, id) 
     ,parentid = IIF(parentid IS NULL,id,parentid) 
    FROM 
     cte 
    WHERE 
     terminalid = @test 

    UNION 

    SELECT DISTINCT 
     id 
     ,parentid = id 
    FROM 
     cte 
    WHERE 
     terminalid = @test 
) 

SELECT 
    id = parentid 
    ,DecendentId = id 
FROM 
    cteAncestors 
ORDER BY 
    id 
    ,DecendentId 

あなたの結果はあなたの2 ancestorsと自己parentidis nullだ元の祖先の場合を除き、そのancestorに関連を与えます。そのnullは私が1分で対処する特別なケースです。

enter image description here

クエリがAncestorsないdescendantsを生産するが、何それはあなたを与えるものではありませんがgrandparent = grandparentparent = parentself = selfを意味する自己参照である。この時点で覚えておいてください。しかし、それを得るためには、すべてidの行を追加し、parentididと同じにするだけです。従ってunion。今すぐあなたの結果セットは、ほぼ完全にシェイプアップされています

enter image description here

null parentidの特殊なケース。したがって、null parentidoriginatingancestorを示し、ancestorにはデータセットにancestorが含まれていません。それをあなたの利益にどのように利用するかがここにあります。 leaf levelで最初の再帰を開始したため、originating ancestorにはじまったidに直接の結びつきはありませんが、それ以外のレベルではnullの親IDを乗っ取って値を反転させるだけで、今度は祖先あなたの葉のために。

enter image description here

はその後、最後に、あなたはそれが子孫のテーブルに列を切り替えるようにしたい場合、あなたは完了です。 1つの最後の注釈 DISTINCTは、 idが追加の parentidで繰り返される場合に存在します。例えば。 6 | 36 | 4

enter image description here

1

これがうまくいくかどうか、あるいはすべての場合に適切な結果が得られるかどうかはわかりませんが、ノードリストをキャプチャしてxml機能を使用して解析してidリストに適用します:

declare @test int = 6; 

;WITH cte AS (SELECT id, parentid, CAST(id AS VARCHAR(MAX)) as IDlist 
       FROM HiearchyTest 
       WHERE isleaf = 1 
       UNION ALL 
       SELECT h.id, h.parentid , CAST(CONCAT(c.IDlist,',',h.id) AS VARCHAR(MAX)) 
       FROM HiearchyTest as h 
       JOIN cte as c 
       ON h.id = c.parentid 
      ) 
    ,cte2 AS (SELECT *, CAST ('<M>' + REPLACE(IDlist, ',', '</M><M>') + '</M>' AS XML) AS Data 
       FROM cte 
       WHERE IDlist LIKE '%'+CAST(@test AS VARCHAR(50))+'%' 
      ) 
SELECT id,Split.a.value('.', 'VARCHAR(100)') AS descendentid 
FROM cte2 a 
CROSS APPLY Data.nodes ('/M') AS Split(a); 
+0

興味深いアプローチ。確かにうまくいくようです。私はマットの答えを受け入れているのは、それが私にとってもっと真っ直ぐで、私が探していたものに近いからです。しかし、これは有効なアプローチのように思われ、私はあなたの答えをupvotedしています。 –

1

ための別のレコードにデータをツリー構造であるため、私たちは(あなたは、あなたがコメントしていないことを言ったにもかかわらず)あなたのニーズを満たすためにhierarchyid型のデータ型を使用することができます。まず、簡単な部分 - 再帰CTE

with cte as (

    select id, parentid, 
     cast(concat('/', id, '/') as varchar(max)) as [path] 
    from [dbo].[HiearchyTest] 
    where ParentID is null 

    union all 

    select child.id, child.parentid, 
     cast(concat(parent.[path], child.id, '/') as varchar(max)) 
    from [dbo].[HiearchyTest] as child 
    join cte as parent 
     on child.parentid = parent.id 
) 
select id, parentid, cast([path] as hierarchyid) as [path] 
into h 
from cte; 

次に、私が書いた小さなテーブル値関数でhierarchyid型を生成:それと

create function dbo.GetAllAncestors(@h hierarchyid, @ReturnSelf bit) 
returns table 
as return 
    select @h.GetAncestor(n.n) as h 
    from dbo.Numbers as n 
    where n.n <= @h.GetLevel() 
     or (@ReturnSelf = 1 and n.n = 0) 

    union all 

    select @h 
    where @ReturnSelf = 1; 

武装は、ご希望の結果セットを取得することはできませんあまりにも悪い:

もちろん
declare @h hierarchyid; 

set @h = (
    select path 
    from h 
    where id = 6 
); 

with cte as (
    select * 
    from h 
    where [path].IsDescendantOf(@h) = 1 
     or @h.IsDescendantOf([path]) = 1 
) 
select h.id as parent, c.id as descendentid 
from cte as c 
cross apply dbo.GetAllAncestors([path], 1) as a 
join h 
    on a.h = h.[path] 
order by h.id, c.id; 

、あなたはそれを永続化しないことによってhierarchyid型を使用する利点の多くを逃している(あなたはサイドテーブルの日付にそれを維持または生成する必要がありますどちらかそれ 毎回)。しかし、そこに行く。

+0

確かにupvoteの価値があります。私はそれが私が探していたものに近いので、マットの答えを受け入れることに終わったが、私もあなたと一緒に遊んで、できる限り学ぶつもりだ。ありがとう。 –

関連する問題