Vòng lặp SQL Server - làm cách nào để lặp qua một tập hợp các bản ghi


151

Làm thế nào để tôi lặp qua một tập hợp các bản ghi từ một lựa chọn?

Vì vậy, ví dụ tôi có một vài bản ghi mà tôi muốn lặp lại và làm một cái gì đó với mỗi bản ghi. Đây là phiên bản nguyên thủy của lựa chọn của tôi:

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

Cảm ơn


5
Bạn muốn làm gì với mỗi bản ghi? Tùy chọn sẽ là thực hiện công việc trong một truy vấn SQL. Chặn rằng bạn sẽ cần sử dụng T-SQL, có lẽ với các con trỏ.
Gordon Linoff

2
Tôi sẽ sử dụng một con trỏ.
FloChanz

5
Điều đó sẽ khá chậm - không thể viết lại Proc được lưu trữ hoặc di chuyển một số logic ra khỏi nó để hoạt động theo cách dựa trên tập hợp?
Cầu

2
@Funky Sproc làm gì? Thông thường mã có thể được viết lại theo cách dựa trên tập hợp (tức là tránh các vòng lặp). Nếu bạn kiên quyết muốn thực hiện thao tác RBAR ( Simple-talk.com/sql/t-sql-programming/, ) thì con trỏ là thứ bạn muốn điều tra.
gvee

1
Có lẽ bạn có thể giải thích những gì bạn sẽ làm với dữ liệu này chi tiết hơn. Trong hầu hết các trường hợp, bạn có thể dễ dàng viết một truy vấn SQL sẽ thực hiện những gì bạn cần thực hiện trong một hành động thay vì lặp qua các bản ghi riêng lẻ.
Alan Barber

Câu trả lời:


212

Bằng cách sử dụng T-SQL và các con trỏ như thế này:

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
Điều đúng đắn là viết lại quá trình để không cần lặp. Looping là một lựa chọn cực kỳ tồi tệ trong cơ sở dữ liệu.
HLGEM

23
Có lẽ bạn đúng nhưng với thông tin được đưa ra trong câu hỏi tại thời điểm tôi viết câu trả lời, người dùng chỉ muốn lặp qua một tập hợp dữ liệu ... và Con trỏ là một cách để làm điều đó.
FloChanz

16
Con trỏ chỉ là một công cụ - nói chung không có gì đúng hay sai về chúng. Quan sát hiệu suất và quyết định. Câu trả lời này (con trỏ) là một lựa chọn có thể. Bạn cũng có thể sử dụng WHILE LOOP, CTE, v.v.
Chuỗi

2
@FrenkyB Có bạn có thể. Nhìn theo cách này ... stackoverflow.com/questions/11035187/ từ
sam yi

2
Xin chúc mừng, giải pháp của bạn thậm chí là trên msdn: msdn.microsoft.com/en-us/l Library / Wiêu và tôi thực sự thích cách bạn sử dụng Kiểu dữ liệu trường.
Pete

110

Đây là những gì tôi đã làm nếu bạn cần làm điều gì đó lặp đi lặp lại ... nhưng sẽ là khôn ngoan khi tìm kiếm các hoạt động thiết lập trước.

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
Sử dụng một HIỆN TẠI (xem câu trả lời dưới đây) dường như là một giải pháp thanh lịch hơn nhiều.
Mikhail Glukhov

Tại sao câu trả lời này có nhiều phiếu bầu hơn giải pháp con trỏ?
ataravati

29
@ataravati Bởi vì giải pháp này đọc sạch hơn cho nhiều lập trình viên hơn là con trỏ. Cú pháp cho con trỏ khá khó xử đối với một số người.
Brian Webster

Cảm ơn bạn! Ví dụ của tôi với cập nhật và nhóm theo logic bằng mã ở trên: pastebin.com/GAjUNNi9 . Có lẽ sẽ hữu ích cho bất cứ ai.
Nigrimmist

biến có thể được sử dụng làm tên cột trong câu lệnh cập nhật bên trong vòng lặp không? Một cái gì đó như "Cập nhật tên bảng SET @ Cột Tên = 2"
MH

28

Thay đổi nhỏ cho câu trả lời của sam yi (để dễ đọc hơn):

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
@bluish, câu trả lời này đang sửa câu trả lời của sam yi. Sự điều chỉnh này chủ yếu bên trong select @TableID = (...)tuyên bố.
Sandman đơn giản

Tôi nghĩ rằng câu trả lời này phải được chọn cho câu hỏi này
sajadre

14

Bằng cách sử dụng con trỏ, bạn có thể dễ dàng lặp lại thông qua các bản ghi riêng lẻ và in các bản ghi riêng biệt hoặc dưới dạng một tin nhắn bao gồm tất cả các bản ghi.

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
Điều này có vẻ thú vị. Tôi tự hỏi định danh @ có nghĩa là gì.
netskink

@ chỉ là để phân biệt như các biến.
Agnel Amodia

9

Chỉ là một cách tiếp cận khác nếu bạn ổn khi sử dụng bảng tạm thời. Tôi đã tự mình kiểm tra điều này và nó sẽ không gây ra ngoại lệ nào (ngay cả khi bảng tạm thời không có bất kỳ dữ liệu nào.)

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

Điều này thật kỳ quái. Nó chứa rất nhiều lỗi, cũng sử dụng hai biến trong đó một biến đi từ 1 đến COUNT(*)thứ hai đi từ COUNT(*)1 là lạ.
David Ferenczy Rogožan 9/2/2016

Biến MAXID được sử dụng để LOOP thông qua. Biến COUNTER được sử dụng để thực hiện thao tác trên một bản ghi cụ thể trong bảng. Nếu tôi đọc câu hỏi, nó nói về "có một vài bản ghi mà tôi muốn lặp lại và làm một cái gì đó với mỗi bản ghi". Tôi có thể sai nhưng xin vui lòng chỉ ra là những gì sai @DAWID trên
Sandeep

2
Tôi nghĩ rõ ràng cách bạn sử dụng các biến đó trong mã của mình. Bạn chỉ có thể có WHILE (@COUTNER <= @ROWID)và bạn không cần phải giảm @ROWIDtrong mỗi lần lặp. Điều gì xảy ra nếu ROWIDs trong bảng của bạn không liên tục (một số hàng đã bị xóa trước đó).
David Ferenczy Rogožan

1
Khi nào bạn sẽ đề xuất sử dụng Bảng tạm thời hơn sử dụng Con trỏ? Đây chỉ là một sự lựa chọn thiết kế, hay một người có hiệu suất tốt hơn?
h0r53

4

Bạn có thể chọn xếp hạng dữ liệu của mình và thêm ROW_NUMBER và đếm ngược về 0 trong khi lặp lại tập dữ liệu của bạn.

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

bằng cách này chúng ta có thể lặp lại thành dữ liệu bảng.

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 là hàm định nghĩa người dùng sẽ phân tích dữ liệu được phân tách bằng dấu phẩy và bảng trả về. cảm ơn


1

Tôi nghĩ rằng đây là ví dụ dễ dàng để lặp lại mục.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.