有没有一种方法可以在不使用游标的情况下遍历TSQL中的表变量?
假设我有以下简单的表变量:
declare @databases table ( DatabaseID int, Name varchar(15), Server varchar(15) ) -- insert a bunch rows into @databases
如果要遍历行,是否声明和使用游标是我唯一的选择?还有另一种方法吗?
解决方案
回答
我们可以使用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
回答
我同意上一篇文章,即基于集合的操作通常会更好地执行,但是如果我们确实需要遍历行,则可以采用以下方法:
- 将新字段添加到表变量中(数据类型位,默认为0)
- 插入数据
- 选择顶部1行,其中fUsed = 0(注意:fUsed是步骤1中字段的名称)
- 执行所需的任何处理
- 通过为记录设置fUsed = 1来更新表变量中的记录
- 从表中选择下一个未使用的记录,然后重复该过程
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,则示例包含:
While (Select Count(*) From #Temp) > 0
会更好地与
While EXISTS(SELECT * From #Temp)
Count必须触摸表中的每一行," EXISTS"只需要触摸第一行。
回答
如果我们别无选择,只能逐行创建一个FAST_FORWARD游标。它就像建立一个while循环一样快,并且从长远来看更容易维护。
FAST_FORWARD
指定启用了性能优化的FORWARD_ONLY,READ_ONLY游标。如果还指定了SCROLL或者FOR_UPDATE,则不能指定FAST_FORWARD。
回答
像这样定义临时表-
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
的问题。
但是,如果我们使用的是SQL Server 2005/2008版,则这是另一种选择
使用递归
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)
这比任何循环技术都快得多,并且更容易编写和维护。