2009-07-28 12 views
3

私は標準的な上司/従業員テーブルを持っています。私は上司(IDで指定された)と彼のすべての従業員(およびその従属者など)を選択する必要があります。残念なことに、実際のデータにはいくつかのループがあります(たとえば、両方の会社の所有者がお互いを上司として設定しています)。 CTEを使った単純な再帰的クエリ(最大再帰レベルが100を超えた)。従業員を選択することはできますか?私は彼らが選ばれた順序ではなく、それぞれが一度選択されただけである。 データのループを含むSQL Server 2005の再帰クエリは可能ですか?


投稿日:私の質問が欲しいですか?うーんOK ...私、それはかなり明白ですが、しかし - ここにある:

with 
UserTbl as -- Selects an employee and his subordinates. 
(
    select a.[User_ID], a.[Manager_ID] from [User] a WHERE [User_ID] = @UserID 
    union all 
    select a.[User_ID], a.[Manager_ID] from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID]) 
) 
select * from UserTbl 


は2を追加しました:ああ、場合には、それは明確ではなかった - これは生産システムであります私は少しアップグレード(基本的に一種のレポートを追加する)をしなければならない。したがって、避けることができればデータを変更しないことをお勧めします。

+0

/あなたの質問を投稿できますか? – Justin

答えて

0

基本的にデータのようなループがある場合は、自分自身で収集ロジックを行う必要があります。 あなたは部下だけを取得し、他の人は上司を得るために1つのcteを使用することができます。

もう1つのアイデアは、両方の会社の所有者に上司としてダミー行を持たせることです。そうすれば、ばかばかしいお互いの上司ではないでしょう。これは私の前向きな選択肢です。

+0

データの固定はオプションですが、最後のオプション(btw - 人が上司を持たない場合もありますので、ダミーユーザーを作成する必要はありません)を残しておきます。 –

+0

また、そこには他のループもあります。 –

+0

唯一の方法は、独自のロジックを作成することです。 –

1

これは最初の再帰的なリンクのために働くだろうが、より長いために動作しない場合がありますが、私がお勧めだろうか

DECLARE @Table TABLE(
     ID INT, 
     PARENTID INT 
) 

INSERT INTO @Table (ID,PARENTID) SELECT 1, 2 

INSERT INTO @Table (ID,PARENTID) SELECT 2, 1 

INSERT INTO @Table (ID,PARENTID) SELECT 3, 1 

INSERT INTO @Table (ID,PARENTID) SELECT 4, 3 

INSERT INTO @Table (ID,PARENTID) SELECT 5, 2 


SELECT * FROM @Table 

DECLARE @ID INT 

SELECT @ID = 1 

;WITH boss (ID,PARENTID) AS (
    SELECT ID, 
      PARENTID 
    FROM @Table 
    WHERE PARENTID = @ID 
), 
bossChild (ID,PARENTID) AS (
    SELECT ID, 
      PARENTID 
    FROM boss 
    UNION ALL 
    SELECT t.ID, 
      t.PARENTID 
    FROM @Table t INNER JOIN 
      bossChild b ON t.PARENTID = b.ID 
    WHERE t.ID NOT IN (SELECT PARENTID FROM boss) 
) 
SELECT * 
FROM bossChild 
OPTION (MAXRECURSION 0) 

は、whileループを使用して、IDがない場合のみ、一時テーブルへのリンクを挿入するためにあるリンク無限ループを除去します。

+0

良い考えですが、あなたが言いました限界を考えれば、vanの簡単な答えを使用するほうが良いでしょう。 –

1

ない汎用的なソリューションが、あなたのケースのために働くかもしれません:あなたの選択クエリにこの変更:になるために

select a.[User_ID], a.[Manager_ID] from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID]) 

を:

select a.[User_ID], a.[Manager_ID] from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID]) 
    and a.[User_ID] <> @UserID 
+0

ちょっといいアイデア!確かに、私はそれが動作すると信じています! :) –

+0

従業員が最大1のマネージャーを持つ場合は、正常に動作するはずです。そうでない場合は、低レベルでループを取得することができ、再帰を停止することはありません。 – van

0

preferrableソリューションは、データをクリーンアップすることであり、将来、ループがないことを確認するために、トリガーまたはUDFを検査制約でラップして実行することができます。私はここに示されたように

ただし、マルチステートメントUDFを使用することができます:あなたはサイクルをフィルタリングするために参加してNOT IN()句を追加することができますAvoiding infinite loops. Part One

+0

本当に、これは私が最後に解決した方法でしたが、問題はまだ有効です。そして、いくつかの構成では、これはとにかく必要なものかもしれません(たとえば、グラフ内のすべてのノードを選択したい特定の開始ノードから到達した)。 –

0

あなたの再帰的なクエリがすでにセットにユーザーIDを追加しないようにするには、いくつかの方法が必要です。ただし、サブクエリと再帰テーブルの二重引用は許可されていません(バン)既にリストに登録されているユーザーを削除するには別の解決策が必要です。

解決策は、これらの行を削除するためにEXCEPTを使用することです。このがマニュアルに従って動作するはずです。ユニオン型演算子にリンクされた複数の再帰文が許可されます。既にリストに含まれているユーザーを削除すると、一定回数反復した後、再帰的な結果セットは空を返し、再帰は停止します。

with UserTbl as -- Selects an employee and his subordinates. 
(
    select a.[User_ID], a.[Manager_ID] from [User] a WHERE [User_ID] = @UserID 
    union all 
    (
     select a.[User_ID], a.[Manager_ID] 
     from [User] a join UserTbl b on (a.[Manager_ID]=b.[User_ID]) 
     where a.[User_ID] not in (select [User_ID] from UserTbl) 
     EXCEPT 
     select a.[User_ID], a.[Manager_ID] from UserTbl a 
    ) 
) 
select * from UserTbl; 

他の選択肢は、反復の固定番号の後にクエリを停止またはMAXRECURSIONクエリオプションヒントを使用するレベルの変数をハードコーディングすることですが、私はそれはあなたが望むものではないと思います。

+0

これは動作しません。複数の再帰的参照はCTEでは許可されていません。 – van

+0

それはCTEでサブクエリを使用することさえ許されていません。 – Matijs

+0

私は今それを解決したと思います。 – Matijs

0

私は2つのアプローチが考えられます。

1)必要以上の行を生成しますが、再帰が深すぎないことを確認するチェックが含まれています。次に、重複したユーザーレコードを削除します。

2)既に訪問したユーザーを保持するために文字列を使用します。サブクエリのアイデアのように、うまくいきませんでした。

アプローチ1:

; with TooMuchHierarchy as (
    select "User_ID" 
     , Manager_ID 
     , 0 as Depth 
    from "User" 
    WHERE "User_ID" = @UserID 
    union all 
    select U."User_ID" 
     , U.Manager_ID 
     , M.Depth + 1 as Depth 
    from TooMuchHierarchy M 
    inner join "User" U 
     on U.Manager_ID = M."user_id" 
    where Depth < 100) -- Warning MAGIC NUMBER!! 
, AddMaxDepth as (
    select "User_ID" 
     , Manager_id 
     , Depth 
     , max(depth) over (partition by "User_ID") as MaxDepth 
    from TooMuchHierarchy) 
select "user_id", Manager_Id 
from AddMaxDepth 
where Depth = MaxDepth 

ラインwhere Depth < 100は最大再帰エラーを得ることからあなたを保持するものです。この数字を小さくすると、捨てる必要のあるレコードが少なくなります。小さすぎると従業員が返されないので、格納されている組織図の深さ以上であることを確認してください。会社が成長するにつれて、不器用な悪夢のようなもの。それより大きい必要がある場合は、option (maxrecursion ... number ...)をすべてのものに追加して、より多くの再帰を許可します。

アプローチ2:あなたが再帰的にそれを行う必要はありません

; with Hierarchy as (
    select "User_ID" 
     , Manager_ID 
     , '#' + cast("user_id" as varchar(max)) + '#' as user_id_list 
    from "User" 
    WHERE "User_ID" = @UserID 
    union all 
    select U."User_ID" 
     , U.Manager_ID 
     , M.user_id_list + '#' + cast(U."user_id" as varchar(max)) + '#' as user_id_list 
    from Hierarchy M 
    inner join "User" U 
     on U.Manager_ID = M."user_id" 
    where user_id_list not like '%#' + cast(U."User_id" as varchar(max)) + '#%') 
select "user_id", Manager_Id 
from Hierarchy 
1

。 WHILEループで実行できます。私はそれがより速くなることを保証する:まあ、私は2つのテクニックについてタイミングを取ったたびに私のためであった。これは非効率的に聞こえるが、ループの数が再帰レベルであるためではない。各反復でループの有無を確認し、どこで発生するのかを修正することができます。ループが発生した場合にエラーを発生させるために一時テーブルに制約を置くこともできますが、ループをより洗練されたものにすることを好みます。また、whileループが特定のレベルのレベルを反復するときにエラーをトリガーすることもできます(検出されないループをキャッチするために、時々発生します)。

トリックは一時表に繰り返し挿入されます現在の反復番号を持つ列を含む)と、一時テーブルの最新の結果と元のテーブルの子エントリとの内部結合を実行します。@@ rowcount = 0のときにループを中断してください! シンプルえっ?

0

これは私が追いかけると階層関係の木ダウンするためのプロジェクトで使用されるコードである。

ユーザー定義関数部下をキャプチャする:管理者をキャプチャする

CREATE FUNCTION fn_UserSubordinates(@User_ID INT) 
RETURNS @SubordinateUsers TABLE (User_ID INT, Distance INT) AS BEGIN 
    IF @User_ID IS NULL 
     RETURN 

    INSERT INTO @SubordinateUsers (User_ID, Distance) VALUES (@User_ID, 0) 

    DECLARE @Distance INT, @Finished BIT 
    SELECT @Distance = 1, @Finished = 0 

    WHILE @Finished = 0 
    BEGIN 
     INSERT INTO @SubordinateUsers 
      SELECT S.User_ID, @Distance 
       FROM Users AS S 
       JOIN @SubordinateUsers AS C 
        ON C.User_ID = S.Manager_ID 
       LEFT JOIN @SubordinateUsers AS C2 
        ON C2.User_ID = S.User_ID 
       WHERE C2.User_ID IS NULL 
     IF @@RowCount = 0 
      SET @Finished = 1 

     SET @Distance = @Distance + 1 
    END 

    RETURN 
END 

ユーザー定義関数:

CREATE FUNCTION fn_UserManagers(@User_ID INT) 
RETURNS @User TABLE (User_ID INT, Distance INT) AS BEGIN 
    IF @User_ID IS NULL 
     RETURN 

    DECLARE @Manager_ID INT 

    SELECT @Manager_ID = Manager_ID 
    FROM UserClasses WITH (NOLOCK) 
    WHERE User_ID = @User_ID 

    INSERT INTO @UserClasses (User_ID, Distance) 
     SELECT User_ID, Distance + 1 
     FROM dbo.fn_UserManagers(@Manager_ID) 

    INSERT INTO @User (User_ID, Distance) VALUES (@User_ID, 0) 

    RETURN 
END 
1

を私はしばらく前にこの質問をしたが、ここでは、無限再帰ループを検出するために働くことが解決策であることを知っています。私はパスを生成し、ユーザーIDがパスに含まれている場合はCTE条件をチェックし、そうであれば再度処理しません。お役に立てれば。

ホセ

DECLARE @Table TABLE(
    USER_ID INT, 
    MANAGER_ID INT) 
INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 1, 2 
INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 2, 1 
INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 3, 1 
INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 4, 3 
INSERT INTO @Table (USER_ID,MANAGER_ID) SELECT 5, 2 

DECLARE @UserID INT 
SELECT @UserID = 1 

;with 
UserTbl as -- Selects an employee and his subordinates. 
(
    select 
     '/'+cast(a.USER_ID as varchar(max)) as [path], 
     a.[User_ID], 
     a.[Manager_ID] 
    from @Table a 
    where [User_ID] = @UserID 
    union all 
    select 
     b.[path] +'/'+ cast(a.USER_ID as varchar(max)) as [path], 
     a.[User_ID], 
     a.[Manager_ID] 
    from @Table a 
    inner join UserTbl b 
     on (a.[Manager_ID]=b.[User_ID]) 
    where charindex('/'+cast(a.USER_ID as varchar(max))+'/',[path]) = 0 
) 
select * from UserTbl 
+0

いいね!おそらくそれほど速くないかもしれませんが、それでも素敵です! :)ああ、そしてbtw - この質問をしたのはジョンでした。 :) –

+0

うーん...これは、重複したユーザーIDがパスの最後にある場合に、これは些細なエラーがあると思います。パスの最後のユーザーIDには末尾にスラッシュが含まれていませんが、charindex()にはそれが必要です。 –

+0

こんにちはVilx、ジョンに電話して申し訳ありませんが、私はジョンが行った編集で混乱しました。 説明したシナリオを唯一の時間とするのは、ユーザーIDが自分自身を指している場合で、その場合、無限ループに入ることなく1回だけ関係が表示されます。時にはバグがフィーチャーになることもありますが、すべてが見る人の目に依存します。私はこの解決策を使用しましたが、IDはユニークな識別子であり、後続のスラッシュを使用する必要はありませんが、数字は別の話です。よろしく。 :) –

2

私はそれがしばらくされている知っているが、私はすべての単一のソリューションを試してみましたが、ここに私の調査結果の概要(多分、このポストであるように私は私の経験を共有する必要があると思いましたか?):

  • 現在のパスの列を追加すると機能しましたが、パフォーマンスが低下していましたので、私にとってはオプションではありません。
  • CTEを使用して行う方法が見つかりませんでした。
  • employeeIdsをテーブルに追加する再帰的なSQL関数を記述しました。循環参照を回避するには、重複IDがテーブルに追加されていないことを確認するチェックがあります。パフォーマンスは平均的でしたが、望ましくありませんでした。

すべてのことを終えて、[適格]従業員のサブセット全体をコード(C#)にダンプしてそこに再帰的な方法でフィルタリングするという考えを思いつきました。次に、フィルタリングされた従業員リストをデータテーブルに書き込んで、それを私のストアドプロシージャに一時テーブルとしてエクスポートしました。私の不信感から、これは小さなテーブルと比較的大きなテーブルの両方で最も高速で柔軟な方法であることが証明されました(私は最大35,000行のテーブルを試しました)。

関連する問題