SQL Server循环-如何遍历一组记录


151

如何遍历select中的一组记录?

举例来说,我有一些记录希望循环浏览,并对每条记录执行某些操作。这是我选择的原始版本:

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

谢谢


5
您想对每条记录做什么?首选是在SQL查询中完成工作。除非您需要使用T-SQL,否则可能需要使用游标。
Gordon Linoff 2013年

2
我会使用游标。
FloChanz 2013年

5
那将非常慢-是否不可能重新编写存储的proc或将其中的某些逻辑移出以基于集合的方式工作?
桥接

2
@Funky该存储库做什么?通常,代码可以以基于集合的方式(即避免循环)来重写。如果您坚决要执行RBAR操作(simple-talk.com/sql/t-sql-programming/…),那么游标就是您要研究的对象。
gvee 2013年

1
也许您可以更详细地解释如何处理这些数据。在大多数情况下,您可以轻松编写一个SQL查询,该查询将完成一项操作即可完成您需要完成的工作,而无需遍历各个记录。
艾伦·巴伯

Answers:


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
正确的做法是重写进程,以便它不需要循环。在数据库中,循环是一个极其糟糕的选择。
HLGEM 2013年

23
也许您是对的,但是根据我撰写答案时问题中给出的信息,用户只想循环访问一组数据...而游标就是一种实现方法。
FloChanz 2013年

16
游标只是一种工具-通常没有关于它的对与错。观察性能并做出决定。这个答案(光标)是一种可能的选择。你也可以使用一个while循环,热膨胀系数等

2
@FrenkyB是的,可以。这样看... stackoverflow.com/questions/11035187/...
SAM义

2
恭喜,您的解决方案甚至位于msdn:msdn.microsoft.com/en-us/library/…上,我真的很喜欢您使用字段数据类型的方式。
皮特2016年

110

如果您需要做一些迭代的事情,这就是我一直在做的事情……但是,首先寻找设置操作是明智的。

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
使用CURSOR(请参阅下面的答案)似乎是一种更为优雅的解决方案。
米哈伊尔·格卢霍夫

为什么这个答案比游标解决方案具有更多投票权?
ataravati 2015年

29
@ataravati因为许多程序员比游标更清晰地读取了此解决方案。游标的语法有些尴尬。
Brian Webster

谢谢!我的示例使用上面的代码进行了更新和按逻辑分组: pastebin.com/GAjUNNi9。也许对任何人都有用。
Nigrimmist 2016年

变量可以在循环内的update语句中用作列名吗?诸如“ 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
@蓝蓝,此答案正在纠正三义的答案。此更正主要在select @TableID = (...)语句内部。
Simple 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到1 COUNT(*),第二个从COUNT(*)1到1是怪异的。
David FerenczyRogožan2016年

变量MAXID用于遍历。变量COUNTER用于对表中的特定记录执行操作。如果我阅读了有关它的问题,那么它谈论的是“有一些记录,我希望遍历这些记录,并对每个记录做些事情”。我可能是错的,但请指出@DAWID上方有什么问题
Sandeep

2
我认为很明显如何在代码中使用这些变量。您可以拥有WHILE (@COUTNER <= @ROWID),而不必@ROWID在每次迭代中递减。顺便说一句,如果ROWID表中的s不连续(先前删除了某些行)会发生什么。
David FerenczyRogožan'16

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.