有没有一种方法可以在不使用游标的情况下遍历TSQL中的表变量?

时间:2020-03-05 18:53:11  来源:igfitidea点击:

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

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)

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