SQL Server CTE และตัวอย่างการเรียกซ้ำ


109

ฉันไม่เคยใช้ CTE กับการเรียกซ้ำ ฉันเพิ่งอ่านบทความเกี่ยวกับเรื่องนี้ บทความนี้แสดงข้อมูลพนักงานด้วยความช่วยเหลือของ Sql server 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 แรกเริ่มทำงานเพียงครั้งเดียวและส่งคืนรหัสพนักงานทั้งหมด

และแบบสอบถามที่สองเริ่มทำงานซ้ำ ๆ โดยค้นหาฐานข้อมูลที่มีพนักงานอยู่ด้วย 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

คำถาม 1) ค่าของ N เพิ่มขึ้นอย่างไร? หากกำหนดค่าให้กับ N ทุกครั้งค่า N สามารถเพิ่มขึ้นได้ แต่จะเริ่มต้นค่า N ครั้งแรกเท่านั้น

Q 2) CTE และการเรียกซ้ำของพนักงานสัมพันธ์:

ขณะที่ฉันเพิ่มผู้จัดการสองคนและเพิ่มพนักงานอีกสองสามคนภายใต้ผู้จัดการคนที่สองคือจุดเริ่มต้นของปัญหา

ฉันต้องการแสดงรายละเอียดผู้จัดการคนแรกและในแถวถัดไปเฉพาะรายละเอียดพนักงานที่เกี่ยวข้องกับผู้ใต้บังคับบัญชาของผู้จัดการคนนั้น

สมมติ

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

คำถาม 1) ค่าของ N เพิ่มขึ้นอย่างไร ถ้าค่าเป็นกำหนดที่จะ N ทุกครั้งที่แล้วค่า N สามารถเพิ่มขึ้น แต่ค่า N ครั้งแรกที่การเริ่มต้น

A1:ในกรณีNนี้ไม่ใช่ตัวแปร Nเป็นนามแฝง มันเทียบเท่ากับSELECT 1 AS N. มันเป็นไวยากรณ์ของความชอบส่วนบุคคล มี 2 ​​วิธีหลักในการตั้งนามแฝงคอลัมน์CTEในT-SQL. ฉันได้รวมอะนาล็อกของวิธีง่ายๆCTEในExcelการลองและแสดงให้เห็นถึงสิ่งที่เกิดขึ้นอย่างคุ้นเคยมากขึ้น

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

Excel_CTE

คำถาม 2) ตอนนี้เกี่ยวกับ CTE และการเรียกซ้ำความสัมพันธ์ของพนักงานเมื่อฉันเพิ่มผู้จัดการสองคนและเพิ่มพนักงานอีกสองสามคนภายใต้ผู้จัดการคนที่สองจากนั้นปัญหาก็เริ่มต้นขึ้น ฉันต้องการแสดงรายละเอียดผู้จัดการคนแรกและในแถวถัดไปจะมีเพียงรายละเอียดพนักงานเหล่านั้นเท่านั้นที่จะมาถึงผู้ที่อยู่ใต้บังคับบัญชาของผู้จัดการคนนั้น

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

อีกหนึ่งตารางที่มีโครงสร้างต้นไม้

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

แบบสอบถามเรียกซ้ำ CTE ไม่ส่งคืนผลลัพธ์ตามที่ฉันต้องการ ฉันต้องการแสดงชื่อผู้จัดการคนแรกจากนั้นแสดงผู้ใต้บังคับบัญชาทั้งหมดของเขาอีกครั้งแสดงชื่อผู้จัดการคนที่สองจากนั้นแสดงผู้ใต้บังคับบัญชาทั้งหมดของเขา ฉันต้องการให้ผลลัพธ์ควรเป็นแบบนี้ ถ้าเป็นไปได้โปรดอัปเดตคำถามของคุณ ขอบคุณ
โทมัส

เพิ่มคอลัมน์ [Family] แล้ว ตรวจสอบเลย
MarkD

ที่นี่ฉันให้ผลลัพธ์ตามที่ฉันต้องการแสดงผล โปรดตรวจสอบและบอกฉันว่าเป็นไปได้ไหม ... ถ้าใช่ให้ทำการแก้ไขที่จำเป็นใน ur sql ขอบคุณสำหรับความพยายามของคุณ
Thomas

ทำไม ';' ก่อนคำสั่ง WITH? "; WITH" ขอบคุณ
Drewdin

2
@ SiKni8 - ลิงค์ดูเหมือนจะตาย
MarkD

11

ต้องการร่างความหมายสั้น ๆ คู่ขนานกับคำตอบที่ถูกต้องอยู่แล้ว

ในแง่ 'ง่าย' CTE แบบเรียกซ้ำสามารถกำหนดตามความหมายเป็นส่วนต่อไปนี้:

1: แบบสอบถาม CTE หรือที่เรียกว่า ANCHOR

2: แบบสอบถาม CTE แบบเรียกซ้ำบน CTE ใน (1) กับ UNION ALL (หรือ UNION หรือ EXCEPT หรือ INTERSECT) ดังนั้นผลลัพธ์สุดท้ายจะถูกส่งกลับตามนั้น

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. ส่งคืนชุดผลลัพธ์ นี่คือ UNION ALL ของ T0 ถึง Tn

-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.