有没有办法循环通过TSQL中的表variables,而不使用游标?

比方说,我有以下简单的表格variables:

declare @databases table ( DatabaseID int, Name varchar(15), Server varchar(15) ) -- insert a bunch rows into @databases 

如果我想遍历行,是声明和使用游标我唯一的select? 有另一种方法吗?

首先,你应该确定你需要遍历每一行 – 基于行动的执行速度会更快,在我能想到的每一种情况下,通常会使用更简单的代码。

根据您的数据,可以循环使用select语句,如下所示:

 Declare @Id int While (Select Count(*) From ATable Where Processed = 0) > 0 Begin Select Top 1 @Id = Id From ATable Where Processed = 0 --Do some processing here Update ATable Set Processed = 1 Where Id = @Id End 

另一种select是使用临时表:

 Select * Into #Temp From ATable Declare @Id int While (Select Count(*) From #Temp) > 0 Begin Select Top 1 @Id = Id From #Temp --Do some processing here Delete #Temp Where Id = @Id End 

您应该select的选项实际上取决于数据的结构和数量。

注意:如果你正在使用SQL Server,你最好使用:

 WHILE EXISTS(SELECT * FROM #Temp) 

使用COUNT将不得不触摸表中的每一行, EXISTS只需要触摸第一个(见Josef的答案 )。

简单的说一下,如果你使用的是SQL Server,那么这个例子有:

 While (Select Count(*) From #Temp) > 0 

会更好地配合

 While EXISTS(SELECT * From #Temp) 

计数将不得不触摸表中的每一行, EXISTS只需要触摸第一个。

这是我如何做到的:

 declare @RowNum int, @CustId nchar(5), @Name1 nchar(25) select @CustId=MAX(USERID) FROM UserIDs --start with the highest ID Select @RowNum = Count(*) From UserIDs --get total number of records WHILE @RowNum > 0 --loop until no more records BEGIN select @Name1 = username1 from UserIDs where USERID= @CustID --get other info from that row print cast(@RowNum as char(12)) + ' ' + @CustId + ' ' + @Name1 --do whatever select top 1 @CustId=USERID from UserIDs where USERID < @CustID order by USERID desc--get the next one set @RowNum = @RowNum - 1 --decrease count END 

没有游标,没有临时表,没有额外的列。 USERID列必须是唯一的整数,因为大多数主键都是。

像这样定义你的临时表 –

 declare @databases table ( RowID int not null identity(1,1) primary key, DatabaseID int, Name varchar(15), Server varchar(15) ) -- insert a bunch rows into @databases 

然后做这个 –

 declare @i int select @i = min(RowID) from @databases declare @max int select @max = max(RowID) from @databases while @i <= @max begin select DatabaseID, Name, Server from @database where RowID = @i --do some stuff set @i = @i + 1 end 

这是我将如何做到这一点:

 Select Identity(int, 1,1) AS PK, DatabaseID Into #T From @databases Declare @maxPK int;Select @maxPK = MAX(PK) From #T Declare @pk int;Set @pk = 1 While @pk <= @maxPK Begin -- Get one record Select DatabaseID, Name, Server From @databases Where DatabaseID = (Select DatabaseID From #T Where PK = @pk) --Do some processing here -- Select @pk = @pk + 1 End 

[编辑]因为我第一次读这个问题的时候可能跳过了“variables”这个词,这里是一个更新的回复…


 declare @databases table ( PK int IDENTITY(1,1), DatabaseID int, Name varchar(15), Server varchar(15) ) -- insert a bunch rows into @databases --/* INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MainDB', 'MyServer' INSERT INTO @databases (DatabaseID, Name, Server) SELECT 1,'MyDB', 'MyServer2' --*/ Declare @maxPK int;Select @maxPK = MAX(PK) From @databases Declare @pk int;Set @pk = 1 While @pk <= @maxPK Begin /* Get one record (you can read the values into some variables) */ Select DatabaseID, Name, Server From @databases Where PK = @pk /* Do some processing here */ /* ... */ Select @pk = @pk + 1 End 

如果没有其他select,可以逐行创buildFAST_FORWARD游标。 这将会像build立一个时间循环一样快,并且更容易维护。

FAST_FORWARD指定启用性能优化的FORWARD_ONLY,READ_ONLY游标。 如果还指定了SCROLL或FOR_UPDATE,则不能指定FAST_FORWARD。

另一种方法,无需更改您的架构或使用临时表:

 DECLARE @rowCount int = 0 ,@currentRow int = 1 ,@databaseID int ,@name varchar(15) ,@server varchar(15); SELECT @rowCount = COUNT(*) FROM @databases; WHILE (@currentRow <= @rowCount) BEGIN SELECT TOP 1 @databaseID = rt.[DatabaseID] ,@name = rt.[Name] ,@server = rt.[Server] FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY t.[DatabaseID], t.[Name], t.[Server] ) AS [RowNumber] ,t.[DatabaseID] ,t.[Name] ,t.[Server] FROM @databases t ) rt WHERE rt.[RowNumber] = @currentRow; EXEC [your_stored_procedure] @databaseID, @name, @server; SET @currentRow = @currentRow + 1; END 

你可以使用while循环:

 While (Select Count(*) From #TempTable) > 0 Begin Insert Into @Databases... Delete From #TempTable Where x = x End 

我真的不明白为什么你需要诉诸使用可怕的cursor 。 但是,如果您正在使用SQL Server版本2005/2008,则还有另一种select
使用recursion

 declare @databases table ( DatabaseID int, Name varchar(15), Server varchar(15) ) --; Insert records into @databases... --; Recurse through @databases ;with DBs as ( select * from @databases where DatabaseID = 1 union all select A.* from @databases A inner join DBs B on A.DatabaseID = B.DatabaseID + 1 ) select * from DBs 
 -- [PO_RollBackOnReject] 'FININV10532' alter procedure PO_RollBackOnReject @CaseID nvarchar(100) AS Begin SELECT * INTO #tmpTable FROM PO_InvoiceItems where CaseID = @CaseID Declare @Id int Declare @PO_No int Declare @Current_Balance Money While (Select ROW_NUMBER() OVER(ORDER BY PO_LineNo DESC) From #tmpTable) > 0 Begin Select Top 1 @Id = PO_LineNo, @Current_Balance = Current_Balance, @PO_No = PO_No From #Temp update PO_Details Set Current_Balance = Current_Balance + @Current_Balance, Previous_App_Amount= Previous_App_Amount + @Current_Balance, Is_Processed = 0 Where PO_LineNumber = @Id AND PO_No = @PO_No update PO_InvoiceItems Set IsVisible = 0, Is_Processed= 0 ,Is_InProgress = 0 , Is_Active = 0 Where PO_LineNo = @Id AND PO_No = @PO_No End End 

轻量级,而不必额外的表,如果你有一个整数的ID在桌子上

 Declare @id int = 0, @anything nvarchar(max) WHILE(1=1) BEGIN Select Top 1 @anything=[Anything],@id=@id+1 FROM Table WHERE ID>@id if(@@ROWCOUNT=0) break; --Process @anything END 

我将提供基于集合的解决scheme。

 insert @databases (DatabaseID, Name, Server) select DatabaseID, Name, Server From ... (Use whatever query you would have used in the loop or cursor) 

这比任何循环技术都快得多,而且易于编写和维护。

这将在SQL SERVER 2012版本中起作用。

 declare @Rowcount int select @Rowcount=count(*) from AddressTable; while( @Rowcount>0) begin select @Rowcount=@Rowcount-1; SELECT * FROM AddressTable order by AddressId desc OFFSET @Rowcount ROWS FETCH NEXT 1 ROWS ONLY; end 

如果您有一个唯一的ID,您可以使用偏移量提取,您可以按以下方式对表格进行sorting:

 DECLARE @TableVariable (ID int, Name varchar(50)); DECLARE @RecordCount int; SELECT @RecordCount = COUNT(*) FROM @TableVariable; WHILE @RecordCount > 0 BEGIN SELECT ID, Name FROM @TableVariable ORDER BY ID OFFSET @RecordCount - 1 FETCH NEXT 1 ROW; SET @RecordCount = @RecordCount - 1; END 

这样我不需要添加字段到表中或使用窗口函数。

可以使用游标来执行此操作:

创build函数[dbo] .f_teste_loop返回@tabela表(cod int,nome varchar(10))作为开始

 insert into @tabela values (1, 'verde'); insert into @tabela values (2, 'amarelo'); insert into @tabela values (3, 'azul'); insert into @tabela values (4, 'branco'); return; 

结束

创build过程[dbo]。[sp_teste_loop]作为开始

 DECLARE @cod int, @nome varchar(10); DECLARE curLoop CURSOR STATIC LOCAL FOR SELECT cod ,nome FROM dbo.f_teste_loop(); OPEN curLoop; FETCH NEXT FROM curLoop INTO @cod, @nome; WHILE (@@FETCH_STATUS = 0) BEGIN PRINT @nome; FETCH NEXT FROM curLoop INTO @cod, @nome; END CLOSE curLoop; DEALLOCATE curLoop; 

结束

这是我的解决scheme,它使用了无限循环, BREAK语句和@@ROWCOUNT函数。 没有游标或临时表是必要的,我只需要写一个查询来获取@databases表中的下一行:

 declare @databases table ( DatabaseID int, [Name] varchar(15), [Server] varchar(15) ); -- Populate the [@databases] table with test data. insert into @databases (DatabaseID, [Name], [Server]) select X.DatabaseID, X.[Name], X.[Server] from (values (1, 'Roger', 'ServerA'), (5, 'Suzy', 'ServerB'), (8675309, 'Jenny', 'TommyTutone') ) X (DatabaseID, [Name], [Server]) -- Create an infinite loop & ensure that a break condition is reached in the loop code. declare @databaseId int; while (1=1) begin -- Get the next database ID. select top(1) @databaseId = DatabaseId from @databases where DatabaseId > isnull(@databaseId, 0); -- If no rows were found by the preceding SQL query, you're done; exit the WHILE loop. if (@@ROWCOUNT = 0) break; -- Otherwise, do whatever you need to do with the current [@databases] table row here. print 'Processing @databaseId #' + cast(@databaseId as varchar(50)); end 

我同意以前的文章,基于集合的操作通常会performance得更好,但如果您确实需要迭代这些行,我会采取以下方法:

  1. 添加一个新的字段到您的表variables(数据types位,默认为0)
  2. 插入你的数据
  3. selectfUsed = 0的前1行(注意:fUsed是步骤1中字段的名称)
  4. 执行你需要做的任何处理
  5. 通过设置logging的fUsed = 1来更新表variables中的logging
  6. 从表中select下一个未使用的logging并重复该过程

     DECLARE @databases TABLE ( DatabaseID int, Name varchar(15), Server varchar(15), fUsed BIT DEFAULT 0 ) -- insert a bunch rows into @databases DECLARE @DBID INT SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0 WHILE @@ROWCOUNT <> 0 and @DBID IS NOT NULL BEGIN -- Perform your processing here --Update the record to "used" UPDATE @databases SET fUsed = 1 WHERE DatabaseID = @DBID --Get the next record SELECT TOP 1 @DBID = DatabaseID from @databases where fUsed = 0 END 

这是我使用2008 R2的代码。 我正在使用的这段代码是在关键字段(SSNO&EMPR_NO)上build立索引

 if object_ID('tempdb..#a')is not NULL drop table #a select 'IF EXISTS (SELECT name FROM sysindexes WHERE name ='+CHAR(39)+''+'IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+char(39)+')' +' begin DROP INDEX [IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+'] ON '+table_schema+'.'+table_name+' END Create index IDX_'+COLUMN_NAME+'_'+SUBSTRING(table_name,5,len(table_name)-3)+ ' on '+ table_schema+'.'+table_name+' ('+COLUMN_NAME+') ' 'Field' ,ROW_NUMBER() over (order by table_NAMe) as 'ROWNMBR' into #a from INFORMATION_SCHEMA.COLUMNS where (COLUMN_NAME like '%_SSNO_%' or COLUMN_NAME like'%_EMPR_NO_') and TABLE_SCHEMA='dbo' declare @loopcntr int declare @ROW int declare @String nvarchar(1000) set @loopcntr=(select count(*) from #a) set @ROW=1 while (@ROW <= @loopcntr) begin select top 1 @String=a.Field from #A a where a.ROWNMBR = @ROW execute sp_executesql @String set @ROW = @ROW + 1 end 

select@pk = @pk + 1会更好:SET @pk + = @pk。 避免使用SELECT如果您不引用表只是分配值。

步骤1:在select语句下面为每个logging创build一个具有唯一行号的临时表。

 select eno,ename,eaddress,mobno int,row_number() over(order by eno desc) as rno into #tmp_sri from emp 

第二步:声明所需的variables

 DECLARE @ROWNUMBER INT DECLARE @ename varchar(100) 

第三步:从临时表中取总行数

 SELECT @ROWNUMBER = COUNT(*) FROM #tmp_sri declare @rno int 

第四步:根据在temp中创build的唯一行号循环临时表

 while @rownumber>0 begin set @rno=@rownumber select @ename=ename from #tmp_sri where rno=@rno **// You can take columns data from here as many as you want** set @rownumber=@rownumber-1 print @ename **// instead of printing, you can write insert, update, delete statements** end 

这种方法只需要一个variables,不会从@databases中删除任何行。 我知道这里有很多答案,但我没有看到一个使用MIN来获得你的下一个ID这样的。

 DECLARE @databases TABLE ( DatabaseID int, Name varchar(15), Server varchar(15) ) -- insert a bunch rows into @databases DECLARE @CurrID INT SELECT @CurrID = MIN(DatabaseID) FROM @databases WHILE @CurrID IS NOT NULL BEGIN -- Do stuff for @CurrID SELECT @CurrID = MIN(DatabaseID) FROM @databases WHERE DatabaseID > @CurrID END