SQL Server loop - ฉันจะวนลูปผ่านชุดของเรคคอร์ด


151

ฉันจะวนซ้ำชุดระเบียนจากตัวเลือกได้อย่างไร

ตัวอย่างเช่นฉันมีบางระเบียนที่ฉันต้องการวนซ้ำและทำบางสิ่งกับแต่ละระเบียน นี่เป็นเวอร์ชั่นดั้งเดิมของการเลือกของฉัน:

select top 1000 * from dbo.table
where StatusID = 7 

ขอบคุณ


5
คุณต้องการทำอะไรกับแต่ละระเบียน การตั้งค่าจะทำงานในแบบสอบถาม SQL ไม่ว่าคุณจะต้องใช้ T-SQL อาจมีเคอร์เซอร์
Gordon Linoff

2
ฉันจะใช้เคอร์เซอร์
FloChanz

5
นั่นจะค่อนข้างช้า - เป็นไปไม่ได้ที่จะเขียน proc ที่เก็บไว้อีกครั้งหรือย้ายตรรกะบางส่วนออกไปเพื่อให้ทำงานในลักษณะที่ตั้งไว้หรือไม่?
สะพาน

2
@Funky sproc ทำอะไรได้บ้าง บ่อยครั้งที่โค้ดสามารถเขียนซ้ำได้ในลักษณะที่เป็นเซต หากคุณยืนกรานคุณต้องการดำเนินการ RBAR ( simple-talk.com/sql/t-sql-programming/… ) เคอร์เซอร์คือสิ่งที่คุณต้องการตรวจสอบ
gvee

1
บางทีคุณสามารถอธิบายสิ่งที่คุณจะทำกับข้อมูลนี้ในรายละเอียดเพิ่มเติม ในกรณีส่วนใหญ่คุณสามารถเขียนแบบสอบถาม SQL เดียวที่จะทำสิ่งที่คุณต้องทำในการกระทำเดียวแทนที่จะวนลูปผ่านแต่ละระเบียน
อลันบาร์เบอร์

คำตอบ:


212

โดยใช้ T-SQL และเคอร์เซอร์เช่นนี้

DECLARE @MyCursor CURSOR;
DECLARE @MyField YourFieldDataType;
BEGIN
    SET @MyCursor = CURSOR FOR
    select top 1000 YourField from dbo.table
        where StatusID = 7      

    OPEN @MyCursor 
    FETCH NEXT FROM @MyCursor 
    INTO @MyField

    WHILE @@FETCH_STATUS = 0
    BEGIN
      /*
         YOUR ALGORITHM GOES HERE   
      */
      FETCH NEXT FROM @MyCursor 
      INTO @MyField 
    END; 

    CLOSE @MyCursor ;
    DEALLOCATE @MyCursor;
END;

5
สิ่งที่ถูกต้องคือการเขียนกระบวนการ teh ใหม่เพื่อที่จะได้ไม่ต้องวนซ้ำ การวนลูปเป็นตัวเลือกที่แย่มากในฐานข้อมูล
HLGEM

23
บางทีคุณพูดถูก แต่ด้วยข้อมูลที่ให้ไว้ในคำถามในขณะที่ฉันเขียนคำตอบที่ผู้ใช้ต้องการวนซ้ำชุดข้อมูล ... และ Cursor เป็นวิธีที่จะทำ
FloChanz

16
เคอร์เซอร์เป็นเพียงเครื่องมือ - โดยทั่วไปแล้วไม่มีอะไรถูกหรือผิดเกี่ยวกับมัน สังเกตประสิทธิภาพและตัดสินใจ คำตอบ (เคอร์เซอร์) นี้เป็นทางเลือกหนึ่งที่เป็นไปได้ นอกจากนี้คุณยังสามารถใช้ WHILE LOOP, CTE และอื่น ๆ ได้อีกด้วย
Chains

2
@FrenkyB ใช่คุณทำได้ ดูวิธีนี้ ... stackoverflow.com/questions/11035187/…
sam yi

2
ขอแสดงความยินดีการแก้ปัญหาของคุณคือแม้ในmsdn : msdn.microsoft.com/en-us/library/ ......และฉันชอบวิธีการที่คุณใช้ประเภทข้อมูลภาคสนาม
Pete

111

นี่คือสิ่งที่ฉันได้ทำถ้าคุณต้องการทำอะไรซ้ำ ๆ ... แต่ก็ควรที่จะมองหาชุดปฏิบัติการก่อน

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select top 1 @TableID = TableID
    from #ControlTable
    order by TableID asc

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable

4
การใช้เคอร์เซอร์ (ดูคำตอบด้านล่าง) ดูเหมือนจะเป็นทางออกที่หรูหรากว่ามาก
Mikhail Glukhov

เหตุใดคำตอบนี้จึงมีคะแนนมากกว่าโซลูชันเคอร์เซอร์?
ataravati

29
@ataravati เพราะวิธีนี้อ่านโปรแกรมเมอร์ได้อย่างหมดจดมากกว่าเคอร์เซอร์ ไวยากรณ์สำหรับเคอร์เซอร์ค่อนข้างน่าอึดอัดใจสำหรับบางคน
Brian Webster

ขอบคุณ! ตัวอย่างของฉันกับการปรับปรุงและกลุ่มโดยใช้ตรรกะข้างต้นรหัส: pastebin.com/GAjUNNi9 อาจจะเป็นประโยชน์กับใครก็ได้
Nigrimmist

ตัวแปรสามารถใช้เป็นชื่อคอลัมน์ในคำสั่ง update ภายในลูปได้หรือไม่? บางอย่างเช่น "อัปเดต TableName SET @ ColumnName = 2"
MH

28

เปลี่ยนเป็นคำตอบของ sam yi เล็กน้อย (เพื่อให้อ่านง่ายขึ้น):

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select @TableID = (select top 1 TableID
                       from #ControlTable
                       order by TableID asc)

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable

1
@ สีน้ำเงินคำตอบนี้แก้ไขคำตอบของ sam yi การแก้ไขนี้ส่วนใหญ่อยู่ในselect @TableID = (...)คำสั่ง
Sandman ง่าย ๆ

ฉันคิดว่าคำตอบนี้จะต้องเลือกคำถามนี้
sajadre

14

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

DECLARE @CustomerID as INT;
declare @msg varchar(max)
DECLARE @BusinessCursor as CURSOR;

SET @BusinessCursor = CURSOR FOR
SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')

OPEN @BusinessCursor;
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @msg = '{
              "CustomerID": "'+CONVERT(varchar(10), @CustomerID)+'",
              "Customer": {
                "LastName": "LastName-'+CONVERT(varchar(10), @CustomerID) +'",
                "FirstName": "FirstName-'+CONVERT(varchar(10), @CustomerID)+'",    
              }
            }|'
        print @msg
    FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
END

1
มันดูน่าสนใจ ฉันสงสัยว่าตัวระบุ @ หมายถึงอะไร
netskink

@ เป็นเพียงการแยกความแตกต่างเป็นตัวแปร
Agnel Amodia

9

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

CREATE TABLE #TempTable
(
    ROWID int identity(1,1) primary key,
    HIERARCHY_ID_TO_UPDATE int,
)

--create some testing data
--INSERT INTO #TempTable VALUES(1)
--INSERT INTO #TempTable VALUES(2)
--INSERT INTO #TempTable VALUES(4)
--INSERT INTO #TempTable VALUES(6)
--INSERT INTO #TempTable VALUES(8)

DECLARE @MAXID INT, @Counter INT

SET @COUNTER = 1
SELECT @MAXID = COUNT(*) FROM #TempTable

WHILE (@COUNTER <= @MAXID)
BEGIN
    --DO THE PROCESSING HERE 
    SELECT @HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
    FROM #TempTable AS PT
    WHERE ROWID = @COUNTER

    SET @COUNTER = @COUNTER + 1
END


IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
BEGIN
    DROP TABLE #TempTable
END

นี่มันแปลกจริงๆ มันมีข้อผิดพลาดจำนวนมากรวมถึงการใช้ตัวแปรสองตัวที่หนึ่งไปจาก 1 ถึงCOUNT(*)และที่สองไปจากCOUNT(*)1 ถึงแปลก
David Ferenczy Rogožan

ตัวแปร MAXID ถูกใช้เพื่อวนผ่าน ตัวแปร COUNTER ถูกใช้เพื่อดำเนินการกับบันทึกเฉพาะในตาราง ถ้าฉันอ่านคำถามที่มันพูดถึง "มีบางบันทึกที่ฉันต้องการวนซ้ำและทำบางสิ่งกับแต่ละระเบียน" ฉันอาจจะผิด แต่โปรดชี้ให้เห็นสิ่งที่ผิดพลาดเหนือ @DAWID
Sandeep

2
ฉันคิดว่ามันชัดเจนว่าคุณใช้ตัวแปรเหล่านั้นในรหัสของคุณอย่างไร คุณสามารถมีได้WHILE (@COUTNER <= @ROWID)และคุณไม่จำเป็นต้องลดค่าลง@ROWIDในการทำซ้ำแต่ละครั้ง BTW จะเกิดอะไรขึ้นถ้าROWIDs ในตารางของคุณไม่ต่อเนื่อง (บางแถวถูกลบไปก่อนหน้านี้)
David Ferenczy Rogožan

1
เมื่อใดที่คุณจะแนะนำให้ใช้ตารางชั่วคราวสำหรับการใช้เคอร์เซอร์? นี่เป็นเพียงตัวเลือกการออกแบบหรือประสิทธิภาพที่ดีขึ้นหรือไม่
h0r53

4

คุณสามารถเลือกที่จะจัดอันดับข้อมูลของคุณและเพิ่ม ROW_NUMBER และนับลงไปที่ศูนย์ในขณะที่ทำซ้ำชุดข้อมูลของคุณ

-- Get your dataset and rank your dataset by adding a new row_number
SELECT  TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
INTO #TEMPTABLE 
FROM DBO.TABLE AS A
WHERE STATUSID = 7;

--Find the highest number to start with
DECLARE @COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
DECLARE @ROW INT;

-- Loop true your data until you hit 0
WHILE (@COUNTER != 0)
BEGIN

    SELECT @ROW = ROW
    FROM #TEMPTABLE
    WHERE ROW = @COUNTER
    ORDER BY ROW DESC

    --DO SOMTHING COOL  

    -- SET your counter to -1
    SET @COUNTER = @ROW -1
END

DROP TABLE #TEMPTABLE

2

ด้วยวิธีนี้เราสามารถทำซ้ำในข้อมูลตาราง

DECLARE @_MinJobID INT
DECLARE @_MaxJobID INT
CREATE  TABLE #Temp (JobID INT)

INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(@JobID,',')
SELECT @_MinJID = MIN(JobID),@_MaxJID = MAX(JobID)  FROM #Temp

    WHILE @_MinJID <= @_MaxJID
    BEGIN

        INSERT INTO Mytable        
        (        
            JobID,        
        )        

        VALUES        
        (        
            @_MinJobID,        
        ) 

        SET @_MinJID = @_MinJID + 1;
    END

DROP TABLE #Temp

STRINGTOTABLEเป็นฟังก์ชั่นกำหนดผู้ใช้ซึ่งจะแยกวิเคราะห์ข้อมูลที่คั่นด้วยเครื่องหมายจุลภาคและส่งคืน ขอบคุณ


1

ฉันคิดว่านี่เป็นตัวอย่างวิธีง่าย ๆ ในการวนซ้ำรายการ

declare @cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'

while (select count(*) from #TempTable) > 0
begin
    select top 1 @cateid = CateID from #TempTable
    print(@cateid)

    --DO SOMETHING HERE

    delete #TempTable where CateID = @cateid
end

drop table #TempTable
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.