web-dev-qa-db-ja.com

ループを回避する再帰的なcte

私はワークフローシステムを探索するために再帰的なCTEを記述しようとしています。残念ながら、ループが原因で最大の再帰エラーが発生しています。

with cteActs as
 (
  select a.id as [id], aa.TASKNEXTID as [childid]
  from TASK a
  inner join TASKNEXT aa on a.id = aa.TASKPARENTID
  where a.id != aa.TASKNEXTID
  ),
 cteNext as
(
    select a.*
    from cteActs as a
    where a.id=42
    union all
    select a.*
    from cteActs as a
    inner join cteNext as n on a.id = n.childid
    )
select * 
from cteNext

テーブルTASKは、42が「ジョブの開始」などのタスクのリストです。 TASKNEXTは、42をTASKテーブルの可能なサブタスクにリンクします。例えば42から43にリンクすることができます。

ID, name, childID
42, Start job, 43
42, Start job, 44
43, Find materials, 200
44, Report to boss, 201
201, Discuss with boss, 202
202, Receive payment, 44

44> 201> 202> 44はクエリがエスケープしないループを作成するので、再帰は死にかけていると思います。どうすればこれを許可できますか?私が読んだほとんどの例/チュートリアルは、厳密な親子関係を前提としています。つまり、子がそれ自体の上位ツリー内の何かの親になることはありません。

私が達成しようとしているのは、タスク42から始まるフローから、または選択した場所に由来するTASKSの明確なリストです。


これは機能する可能性のある反復2ですが、実行速度が非常に遅くなります。

  select a.id as [id], aa.ACTIONACTIVITYID as [childid]
   into #temp
  from TASK a
  inner joinTASKNEXT aa on a.id = aa.TASKPARENTID
  where a.id != aa.TASKNEXTID    
  create clustered index [hello] on #temp (ID ASC)
  create nonclustered index [hello2] on #temp (childid ASC)
  ;
with cteNext as
(
    select a.*, 
    cast(',' + cast(a.ID as varchar(10)) + ',' as varchar(max)) as Path,
    0 as [cyc]
    from #temp as a
    where a.id=42
    union all
    select a.*,
    n.Path + cast(a.ID as varchar(10)) + ',',
         case when n.Path like '%,'+cast(a.ID as varchar(10))+',%' 
           then 1 
           else 0 
         end as [cyc]
    from #temp as a
    inner join cteNext as n on a.id = n.childid
    where n.cyc = 0
    )

select   id, childid
from cteNext
where cyc =0

実行計画

2
Paul

だから私が思いついた最良の結果は、次の改善を追加することです
-クエリされたテーブルの最適化(提案されたインデックスを持つ新しい一時テーブルの作成)
-パス要素を追加して、パスの既存の部分を再訪していないことを確認します
-リミッターとして深度カウンターを追加します。しかし、これは私が意識的に完全な結果セットを持たないことを選択していることを意味します

  select a.id as [id], aa.ACTIONACTIVITYID as [childid]
   into #temp
  from TASK a
  inner join TASKNEXT aa on a.id = aa.TASKPARENTID
  where a.id != aa.TASKNEXTID

  create clustered index [hello] on #temp (ID ASC)
  create nonclustered index [hello2] on #temp (childid ASC)
  ;
  drop table #temp2;
with cteNext as
(
    select a.*, 
    cast(',' + cast(a.ID as varchar(10)) + ',' as varchar(max)) as Path,
    0 as [cyc], 0 as [depth]
    from #temp as a
    where a.id=42
    union all
    select a.*,
    n.Path + cast(a.ID as varchar(10)) + ',',
         case when n.Path like '%,'+cast(a.ID as varchar(10))+',%' 
           then 1 
           else 0 
         end as [cyc], n.depth+1 as [depth]
    from #temp as a
    inner join cteNext as n on a.id = n.childid
    where n.cyc = 0 and n.depth <=11
    )

select *
into #temp2
from cteNext
where cyc =0

このデータの最初の目的は「ふわふわ」することです。ワークフローを表示するためにグラフマップを生成しているため、少なくとも最初のインスタンスでは限られた深さで十分です。しかし、誰かが持っていればより良い答えが受け入れられます

1
Paul