วิธีที่ง่ายที่สุดในการเข้าร่วมด้วยตนเองแบบเรียกซ้ำ?


102

วิธีที่ง่ายที่สุดในการเข้าร่วมด้วยตนเองแบบเรียกซ้ำใน SQL Server คืออะไร ฉันมีโต๊ะแบบนี้:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2
5          YT         NULL
6          IS         5

และฉันต้องการรับเรกคอร์ดที่เกี่ยวข้องกับลำดับชั้นโดยเริ่มจากบุคคลที่ระบุเท่านั้น ดังนั้นหากฉันขอลำดับชั้นของ CJ ตาม PersonID = 1 ฉันจะได้รับ:

PersonID | Initials | ParentID
1          CJ         NULL
2          EB         1
3          MB         1
4          SW         2

และสำหรับ EB ฉันจะได้รับ:

PersonID | Initials | ParentID
2          EB         1
4          SW         2

ฉันติดอยู่เล็กน้อยไม่สามารถคิดว่าจะทำอย่างไรนอกเหนือจากการตอบสนองเชิงลึกคงที่ตามการรวมกลุ่ม สิ่งนี้จะเกิดขึ้นเพราะเราจะไม่มีหลายระดับ แต่ฉันต้องการทำให้ถูกต้อง

ขอบคุณ! คริส.


2
คุณกำลังใช้ SQL Server เวอร์ชันใด เช่น Sql 2000, 2005, 2008?
boydc7

2
คำถามดังนั้นเกี่ยวกับการสืบค้นแบบเรียกซ้ำ: stackoverflow.com/search?q=sql-server+recursive
OMG Ponies

คำตอบ:


115
WITH    q AS 
        (
        SELECT  *
        FROM    mytable
        WHERE   ParentID IS NULL -- this condition defines the ultimate ancestors in your chain, change it as appropriate
        UNION ALL
        SELECT  m.*
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q

เมื่อเพิ่มเงื่อนไขการสั่งซื้อคุณสามารถรักษาลำดับต้นไม้ได้:

WITH    q AS 
        (
        SELECT  m.*, CAST(ROW_NUMBER() OVER (ORDER BY m.PersonId) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN AS bc
        FROM    mytable m
        WHERE   ParentID IS NULL
        UNION ALL
        SELECT  m.*,  q.bc + '.' + CAST(ROW_NUMBER() OVER (PARTITION BY m.ParentID ORDER BY m.PersonID) AS VARCHAR(MAX)) COLLATE Latin1_General_BIN
        FROM    mytable m
        JOIN    q
        ON      m.parentID = q.PersonID
        )
SELECT  *
FROM    q
ORDER BY
        bc

เมื่อเปลี่ยนORDER BYเงื่อนไขคุณสามารถเปลี่ยนลำดับของพี่น้องได้


7
+1 ยกเว้นว่าจะต้องคริสแทนPersonID = theIdYouAreLookingFor ParentID IS NULL
Heinzi

ฉันได้โพสต์คำถามใหม่ใน SO stackoverflow.com/questions/13535003/…
Kishore Kumar

@Aaroninus: โหนดแม่ถูกกำหนดโดยแบบสอบถามบนสุด (จุดยึด) ในWITHประโยค หากคุณต้องการข้อมูลเฉพาะโปรดสร้างซอบนsqlfiddle.comและโพสต์ลิงค์ที่นี่
Quassnoi

ข้อความค้นหานี้ใช้ไม่ได้สำหรับฉันจนกว่าฉันจะเปลี่ยนบรรทัดแรกเป็น "WITH RECURSIVE q AS" สำหรับการอ้างอิงฉันใช้ "10.3.23-MariaDB-0 + deb10u1"
Josh McGee

@JoshMcGee: เหตุใดคุณจึงคาดหวังให้แบบสอบถาม SQL Server ทำงานบน MariaDB โดยไม่มีการเปลี่ยนแปลง
Quassnoi

25

การใช้ CTE คุณสามารถทำได้ด้วยวิธีนี้

DECLARE @Table TABLE(
        PersonID INT,
        Initials VARCHAR(20),
        ParentID INT
)

INSERT INTO @Table SELECT     1,'CJ',NULL
INSERT INTO @Table SELECT     2,'EB',1
INSERT INTO @Table SELECT     3,'MB',1
INSERT INTO @Table SELECT     4,'SW',2
INSERT INTO @Table SELECT     5,'YT',NULL
INSERT INTO @Table SELECT     6,'IS',5

DECLARE @PersonID INT

SELECT @PersonID = 1

;WITH Selects AS (
        SELECT *
        FROM    @Table
        WHERE   PersonID = @PersonID
        UNION ALL
        SELECT  t.*
        FROM    @Table t INNER JOIN
                Selects s ON t.ParentID = s.PersonID
)
SELECT  *
FROm    Selects

2
ตอบได้ครบถ้วนที่สำคัญ WHERE PersonID = @PersonID
Oli B

5

แบบสอบถาม Quassnoi พร้อมการเปลี่ยนแปลงสำหรับตารางขนาดใหญ่ พ่อแม่ที่มีลูกมากกว่า 10 คน: สร้างเป็น str (5) the row_number ()

ด้วย q AS 
        (
        SELECT m. *, CAST (str (ROW_NUMBER () OVER (ORDER BY m.ordernum), 5) AS VARCHAR (MAX)) COLLATE Latin1_General_BIN AS bc
        จาก # ม
        WHERE ParentID = 0
        ยูเนี่ยนทั้งหมด
        เลือกม. *, q.bc + '.' + str (ROW_NUMBER () OVER (PARTITION BY m.ParentID ORDER BY m.ordernum), 5) COLLATE Latin1_General_BIN
        จาก # ม
        เข้าร่วม q
        บน m.parentID = q.DBID
        )
เลือก *
จาก q
สั่งโดย
        bc


2

SQL 2005 หรือใหม่กว่า CTE เป็นวิธีมาตรฐานในการดำเนินการตามตัวอย่างที่แสดง

SQL 2000 คุณสามารถทำได้โดยใช้ UDF -

CREATE FUNCTION udfPersonAndChildren
(
    @PersonID int
)
RETURNS @t TABLE (personid int, initials nchar(10), parentid int null)
AS
begin
    insert into @t 
    select * from people p      
    where personID=@PersonID

    while @@rowcount > 0
    begin
      insert into @t 
      select p.*
      from people p
        inner join @t o on p.parentid=o.personid
        left join @t o2 on p.personid=o2.personid
      where o2.personid is null
    end

    return
end

(ซึ่งจะใช้งานได้ในปี 2548 มันไม่ใช่วิธีมาตรฐานในการทำ แต่ถ้าคุณพบว่าวิธีที่ง่ายกว่าในการทำงานให้รันด้วย)

หากคุณต้องการทำสิ่งนี้ใน SQL7 จริงๆคุณสามารถทำได้ประมาณข้างต้นใน sproc แต่ไม่สามารถเลือกได้ - SQL7 ไม่รองรับ UDF


2

ตรวจสอบสิ่งต่อไปนี้เพื่อช่วยให้เข้าใจแนวคิดของการเรียกซ้ำ CTE

DECLARE
@startDate DATETIME,
@endDate DATETIME

SET @startDate = '11/10/2011'
SET @endDate = '03/25/2012'

; WITH CTE AS (
    SELECT
        YEAR(@startDate) AS 'yr',
        MONTH(@startDate) AS 'mm',
        DATENAME(mm, @startDate) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        @startDate 'new_date'
    UNION ALL
    SELECT
        YEAR(new_date) AS 'yr',
        MONTH(new_date) AS 'mm',
        DATENAME(mm, new_date) AS 'mon',
        DATEPART(d,@startDate) AS 'dd',
        DATEADD(d,1,new_date) 'new_date'
    FROM CTE
    WHERE new_date < @endDate
    )
SELECT yr AS 'Year', mon AS 'Month', count(dd) AS 'Days'
FROM CTE
GROUP BY mon, yr, mm
ORDER BY yr, mm
OPTION (MAXRECURSION 1000)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.