3

標準のボス/部下の従業員テーブルがあります。上司(IDで指定)とそのすべての部下(およびそのサブロディネートなど)を選択する必要があります。残念ながら、実際のデータにはいくつかのループがあります(たとえば、両方の会社の所有者がお互いを上司として設定しています)。CTEを使用した単純な再帰クエリはこれを抑制します(最大再帰レベル100を超えました)。それでも従業員を選べますか?私はそれらが選択される順序は気にせず、それぞれが一度だけ選択されるだけです。


追加:私のクエリが必要ですか?うーん...わかりました...私はそれはかなり明白ですが、-ここにあります:

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:ああ、はっきりしなかった場合に備えて、これは本番システムであり、少しアップグレードする必要があります(基本的に一種のレポートを追加します)。したがって、回避できる場合は、データを変更しないことをお勧めします。

4

10 に答える 10

2

しばらく時間が経っていることは承知していますが、すべてのソリューションを試したので、私の経験を共有する必要があると考えました。

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

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

于 2011-12-16T03:51:52.607 に答える
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がまだ存在しない場合にのみ一時テーブルにリンクを挿入して、無限ループを削除することです。

于 2009-07-28T10:10:31.647 に答える
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
于 2009-07-28T10:40:01.933 に答える
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
于 2009-12-31T17:13:33.390 に答える
1

再帰的に行う必要はありません。WHILE ループで実行できます。私はそれがより速いことを保証します.2つのテクニックでタイミングを計るたびに、私にとってはそうでした. これは非効率に聞こえますが、ループの数が再帰レベルであるため、そうではありません。反復ごとに、ループをチェックし、ループが発生した場所を修正できます。ループが発生した場合にエラーを発生させるように一時テーブルに制約を設定することもできますが、ループをよりエレガントに処理するものを好むようです。while ループが一定数のレベルを反復するときにエラーをトリガーすることもできます (未検出のループをキャッチするためですか? - ああ、時々発生します。

トリックは、現在の繰り返し番号を持つ列を含む一時テーブル (ルート エントリでプライミングされている) に繰り返し挿入し、一時テーブルの最新の結果と元の子エントリの間で内部結合を行うことです。テーブル。@@rowcount=0 のときにループから抜け出してください! シンプルでしょ?

于 2009-07-28T18:01:22.223 に答える
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
于 2009-07-28T17:11:57.537 に答える
0

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

別のアイデアは、両方の会社の所有者に上司としてダミーの列を設けて、お互いが上司にならないようにすることです。これはばかげています。これは私の好みのオプションです。

于 2009-07-28T09:41:33.297 に答える
0

望ましい解決策は、データをクリーンアップし、今後ループが発生しないようにすることです。これは、トリガーまたはチェック制約でラップされた UDF を使用して実行できます。

ただし、ここで示したように、複数ステートメントの UDF を使用できます:無限ループの回避。パート1

結合に NOT IN() 句を追加して、サイクルを除外できます。

于 2009-07-28T13:27:37.767 に答える
0

再帰クエリがユーザー ID を既にセットに追加するのを防ぐには、いくつかの方法が必要です。ただし、サブクエリと再帰テーブルの二重言及は許可されていないため ( vanに感謝します)、既にリストにあるユーザーを削除するには別のソリューションが必要です。

解決策は、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;

もう 1 つのオプションは、固定回数の反復後にクエリを停止するレベル変数をハードコードするか、MAXRECURSION クエリ オプション ヒントを使用することですが、それはあなたが望むものではないと思います。

于 2009-07-28T13:52:58.827 に答える
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
于 2009-07-28T20:02:16.677 に答える