是否有一种方法在TSQL中循环遍历表变量而不使用游标?

假设我有以下简单的表变量:

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

如果我想遍历各行,声明和使用游标是我唯一的选择吗?还有别的办法吗?

778701 次浏览

你可以使用while循环:

While (Select Count(*) From #TempTable) > 0
Begin
Insert Into @Databases...


Delete From #TempTable Where x = x
End

首先,你应该绝对确定你需要遍历每一行——基于行集的操作在我能想到的任何情况下都会执行得更快,通常会使用更简单的代码。

根据你的数据,可能只使用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 *
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

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

注意:如果你正在使用SQL Server,你将更好地使用:

WHILE EXISTS(SELECT * FROM #Temp)

使用COUNT将不得不触及表中的每一行,而EXISTS只需要触及第一行(参见下面的约瑟夫的回答)。

以下是我的做法:

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

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


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

我同意之前的帖子,基于集的操作通常会执行得更好,但如果你确实需要遍历行,下面是我将采取的方法:

  1. 添加一个新的字段到你的表变量(数据类型位,默认为0)
  2. 插入数据
  3. 选择前1行,其中fUsed = 0 (注:fUsed是第一步的字段名)
  4. 执行您需要做的任何处理
  5. 通过为记录设置fUsed = 1来更新表变量中的记录
  6. 从表中选择下一条未使用的记录并重复此过程

    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
    

简单说明一下,如果你使用的是SQL Server(2008及以上版本),下面的例子有:

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

会不会更好

While EXISTS(SELECT * From #Temp)

Count将不得不触及表中的每一行,EXISTS只需要触及第一行。

如果您别无选择,只能逐行创建FAST_FORWARD游标。它和构建while循环一样快,而且更容易长期维护。

< p > FAST_FORWARD 指定一个启用性能优化的FORWARD_ONLY、READ_ONLY游标。如果还指定了SCROLL或FOR_UPDATE,则FAST_FORWARD不能指定。< / p >

像这样定义临时表-

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
我真的不明白为什么你需要使用可怕的cursor。 但是如果你使用的是2005/2008版本的SQL Server,还有另一种选择
使用递归 < / p >
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

我是这样做的:

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列必须是唯一的整数,就像大多数主键一样

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

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

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

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

另一种不需要改变模式或使用临时表的方法:

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

这将在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

这是我使用的2008 R2代码。我使用的这段代码是在关键字段上建立索引(SSNO &都是传说

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如果你没有引用表,只是分配值。

Step1:下面的select语句为每条记录创建一个具有唯一行号的临时表。

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

步骤2:声明所需的变量

DECLARE @ROWNUMBER INT
DECLARE @ename varchar(100)

步骤3:从临时表中获取总行数

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

Step4:根据在temp中创建的唯一行号循环temp表

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

轻量级,不需要创建额外的表,如果你在表上有一个整数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

这种方法只需要一个变量,并且不会从@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

我更喜欢使用偏移获取,如果你有一个唯一的ID,你可以排序你的表:

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

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

可以使用游标来做到这一点:

创建函数[dbo]. f_test_loop 返回@tabela表 ( 鳕鱼int, 省varchar (10) ) 作为 开始< / p >

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;

结束

创建过程[dbo].[sp_test_loop] 作为 开始< / p >

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;

结束

下面是我的解决方案,它使用了一个无限循环、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