SQL Server CTEと再帰の例


109

再帰でCTEを使用することはありません。私はそれに関する記事を読んでいました。この記事では、SQLサーバーのCTEと再帰を利用して従業員情報を示します。基本的には、従業員とそのマネージャー情報を表示しています。このクエリの仕組みを理解できません。これがクエリです:

WITH
  cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
  AS
  (
    SELECT EmployeeID, FirstName, LastName, ManagerID, 1
    FROM Employees
    WHERE ManagerID IS NULL
    UNION ALL
    SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
      r.EmpLevel + 1
    FROM Employees e
      INNER JOIN cteReports r
        ON e.ManagerID = r.EmpID
  )
SELECT
  FirstName + ' ' + LastName AS FullName,
  EmpLevel,
  (SELECT FirstName + ' ' + LastName FROM Employees
    WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID

ここに私は出力がどのように表示されているかについて投稿しています: ここに画像の説明を入力してください

私はそれが最初にマネージャーを示し、次にループで彼の部下をどのように表示しているかを知る必要があるだけです。最初のSQLステートメントは1回だけ起動し、すべての従業員IDを返すと思います。

そして、2番目のクエリが繰り返し実行され、現在のマネージャーIDで従業員が存在するデータベースにクエリを実行します。

SQLステートメントが内部ループでどのように実行されるかを説明し、SQLの実行順序も教えてください。ありがとう。

質問の私の第2フェーズ

;WITH Numbers AS
(
    SELECT n = 1
    UNION ALL
    SELECT n + 1
    FROM Numbers
    WHERE n+1 <= 10
)
SELECT n
FROM Numbers

Q 1)Nの値はどのように増加しますか?値が毎回Nに割り当てられている場合、N値は増分できますが、N値が最初に初期化されたときのみです。

Q 2)CTEと従業員関係の再帰:

2人のマネージャーを追加し、2人目のマネージャーの下に数人の従業員を追加した瞬間に問題が始まります。

最初のマネージャーの詳細を表示し、次の行には、そのマネージャーの部下に関連する従業員の詳細のみを表示します。

と思います

ID     Name      MgrID    Level
---    ----      ------   -----
1      Keith      NULL     1
2      Josh       1        2
3      Robin      1        2
4      Raja       2        3
5      Tridip     NULL     1
6      Arijit     5        2
7      Amit       5        2
8      Dev        6        3

結果をCTE式で表示したい。ここでは、マネージャーと従業員の関係をプルするためにSQLで何を変更するかを教えてください。ありがとう。

出力を次のようにしたい:

ID          Name   MgrID       nLevel      Family
----------- ------ ----------- ----------- --------------------
1           Keith  NULL        1           1
3           Robin  1           2           1
2           Josh   1           2           1
4           Raja   2           3           1
5           Tridip NULL        1           2
7           Amit   5           2           2
6           Arijit 5           2           2
8           Dev    6           3           2

これは可能ですか...?

回答:


210

私はあなたのコードをテストしていませんが、それがコメントでどのように動作するかを理解するのを手助けしようとしました。

WITH
  cteReports (EmpID, FirstName, LastName, MgrID, EmpLevel)
  AS
  (
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
-- In a rCTE, this block is called an [Anchor]
-- The query finds all root nodes as described by WHERE ManagerID IS NULL
    SELECT EmployeeID, FirstName, LastName, ManagerID, 1
    FROM Employees
    WHERE ManagerID IS NULL
-->>>>>>>>>>Block 1>>>>>>>>>>>>>>>>>
    UNION ALL
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>    
-- This is the recursive expression of the rCTE
-- On the first "execution" it will query data in [Employees],
-- relative to the [Anchor] above.
-- This will produce a resultset, we will call it R{1} and it is JOINed to [Employees]
-- as defined by the hierarchy
-- Subsequent "executions" of this block will reference R{n-1}
    SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,
      r.EmpLevel + 1
    FROM Employees e
      INNER JOIN cteReports r
        ON e.ManagerID = r.EmpID
-->>>>>>>>>>Block 2>>>>>>>>>>>>>>>>>
  )
SELECT
  FirstName + ' ' + LastName AS FullName,
  EmpLevel,
  (SELECT FirstName + ' ' + LastName FROM Employees
    WHERE EmployeeID = cteReports.MgrID) AS Manager
FROM cteReports
ORDER BY EmpLevel, MgrID

CTEその操作を説明するために考えることができる再帰の最も単純な例は次のとおりです。

;WITH Numbers AS
(
    SELECT n = 1
    UNION ALL
    SELECT n + 1
    FROM Numbers
    WHERE n+1 <= 10
)
SELECT n
FROM Numbers

Q 1)Nの値が増加している方法。値が毎回Nに割り当てられる場合、N値はインクリメントできますが、N値が初期化されたのは初めての場合のみです。

A1:この場合、Nは変数ではありません。Nエイリアスです。と同等ですSELECT 1 AS N。それは個人の好みの構文です。で列をエイリアシングの2の主な方法があるCTEではT-SQL。私は簡単なのアナログ含めましたCTEExcel試してみて、何が起こっているかをより身近な方法で説明するために。

--  Outside
;WITH CTE (MyColName) AS
(
    SELECT 1
)
-- Inside
;WITH CTE AS
(
    SELECT 1 AS MyColName
    -- Or
    SELECT MyColName = 1  
    -- Etc...
)

Excel_CTE

Q 2)ここで、CTEと従業員関係の再帰についてここで、2人のマネージャーを追加し、2人目のマネージャーの下に数人の従業員を追加すると、問題が発生します。最初のマネージャーの詳細を表示したいのですが、次の行には、そのマネージャーの部下である従業員の詳細のみが表示されます

A2:

このコードはあなたの質問に答えますか?

--------------------------------------------
-- Synthesise table with non-recursive CTE
--------------------------------------------
;WITH Employee (ID, Name, MgrID) AS 
(
    SELECT 1,      'Keith',      NULL   UNION ALL
    SELECT 2,      'Josh',       1      UNION ALL
    SELECT 3,      'Robin',      1      UNION ALL
    SELECT 4,      'Raja',       2      UNION ALL
    SELECT 5,      'Tridip',     NULL   UNION ALL
    SELECT 6,      'Arijit',     5      UNION ALL
    SELECT 7,      'Amit',       5      UNION ALL
    SELECT 8,      'Dev',        6   
)
--------------------------------------------
-- Recursive CTE - Chained to the above CTE
--------------------------------------------
,Hierarchy AS
(
    --  Anchor
    SELECT   ID
            ,Name
            ,MgrID
            ,nLevel = 1
            ,Family = ROW_NUMBER() OVER (ORDER BY Name)
    FROM Employee
    WHERE MgrID IS NULL

    UNION ALL
    --  Recursive query
    SELECT   E.ID
            ,E.Name
            ,E.MgrID
            ,H.nLevel+1
            ,Family
    FROM Employee   E
    JOIN Hierarchy  H ON E.MgrID = H.ID
)
SELECT *
FROM Hierarchy
ORDER BY Family, nLevel

ツリー構造を持つ別のSQL

SELECT ID,space(nLevel+
                    (CASE WHEN nLevel > 1 THEN nLevel ELSE 0 END)
                )+Name
FROM Hierarchy
ORDER BY Family, nLevel

CTE再帰クエリは、希望する方法で結果を返しません。最初のマネージャー名を表示してから、すべての部下を再度表示し、2番目のマネージャー名を表示してから、すべての部下を表示したい。この方法で出力する必要があります。可能であれば、クエリを更新してください。感謝
トーマス

[家族]列を追加しました。今すぐ確認してください。
MarkD 2013年

ここで、結果を表示する方法を出力に指定します。チェックして、それが可能かどうか教えてください...もしそうであれば、ur sqlで必要な変更を行ってください。あなたの努力に感謝します。
Thomas

なぜ「;」なのか WITHステートメントの前?"; WITH"ありがとう
ドリューディン

2
@ SiKni8-リンクが機能していないようです
MarkD '10 / 10/21

11

すでに正しい答えに対応する簡単な意味論を概説したいと思います。

「単純な」用語では、再帰的CTEは意味的に次の部分として定義できます。

1:CTEクエリ。ANCHORとも呼ばれます。

2:UNION ALL(またはUNIONまたはEXCEPTまたはINTERSECT)を使用した(1)のCTEに対する再帰CTEクエリ。したがって、最終的な結果がそれに応じて返されます。

3:コーナー/終了条件。これは、再帰クエリによって返される行/タプルがなくなったときのデフォルトです。

画像を明確にする短い例:

;WITH SupplierChain_CTE(supplier_id, supplier_name, supplies_to, level)
AS
(
SELECT S.supplier_id, S.supplier_name, S.supplies_to, 0 as level
FROM Supplier S
WHERE supplies_to = -1    -- Return the roots where a supplier supplies to no other supplier directly

UNION ALL

-- The recursive CTE query on the SupplierChain_CTE
SELECT S.supplier_id, S.supplier_name, S.supplies_to, level + 1
FROM Supplier S
INNER JOIN SupplierChain_CTE SC
ON S.supplies_to = SC.supplier_id
)
-- Use the CTE to get all suppliers in a supply chain with levels
SELECT * FROM SupplierChain_CTE

説明:最初のCTEクエリは、他のサプライヤーに直接供給しないベースサプライヤー(葉など)を返します(-1)

最初の反復の再帰クエリは、ANCHORによって返されたサプライヤに供給するすべてのサプライヤを取得します。このプロセスは、条件がタプルを返すまで続きます。

UNION ALLは、再帰呼び出し全体のすべてのタプルを返します。

別の良い例がここにあります

PS:再帰的なCTEが機能するためには、関係が機能するための階層的な(再帰的な)条件が必要です。例:elementId = elementParentId ..ポイントを取得します。


9

実行プロセスは再帰CTEと本当に混乱しています。https://technet.microsoft.com/en-us/library/ms186243(v = sql.105).aspxとCTE実行プロセスの概要で最良の答えを見つけました以下の通りです。

再帰実行のセマンティクスは次のとおりです。

  1. CTE式をアンカーメンバーと再帰メンバーに分割します。
  2. 最初の呼び出しまたは基本結果セット(T0)を作成するアンカーメンバーを実行します。
  3. 入力としてTiを使用し、出力としてTi + 1を使用して再帰メンバーを実行します。
  4. 空のセットが返されるまで、手順3を繰り返します。
  5. 結果セットを返します。これはT0からTnのUNION ALLです。

-4
    --DROP TABLE #Employee
    CREATE TABLE #Employee(EmpId BIGINT IDENTITY,EmpName VARCHAR(25),Designation VARCHAR(25),ManagerID BIGINT)

    INSERT INTO #Employee VALUES('M11M','Manager',NULL)
    INSERT INTO #Employee VALUES('P11P','Manager',NULL)

    INSERT INTO #Employee VALUES('AA','Clerk',1)
    INSERT INTO #Employee VALUES('AB','Assistant',1)
    INSERT INTO #Employee VALUES('ZC','Supervisor',2)
    INSERT INTO #Employee VALUES('ZD','Security',2)


    SELECT * FROM #Employee (NOLOCK)

    ;
    WITH Emp_CTE 
    AS
    (
        SELECT EmpId,EmpName,Designation, ManagerID
              ,CASE WHEN ManagerID IS NULL THEN EmpId ELSE ManagerID END ManagerID_N
        FROM #Employee  
    )
    select EmpId,EmpName,Designation, ManagerID
    FROM Emp_CTE
    order BY ManagerID_N, EmpId

1
再帰的な CTEは含まれていないので、これは質問に答えることさえできないコードのみの答えです。
Dragomok
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.