有没有办法在不使用游标的情况下在TSQL中循环表变量?

Ray*_*ega 234 t-sql sql-server loops

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

declare @databases table
(
    DatabaseID    int,
    Name        varchar(15),   
    Server      varchar(15)
)
-- insert a bunch rows into @databases
Run Code Online (Sandbox Code Playgroud)

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

Mar*_*nnw 352

首先,您应该绝对确定需要遍历每一行 - 基于集合的操作在我能想到的每种情况下都会执行得更快,并且通常会使用更简单的代码.

根据您的数据,可以使用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
Run Code Online (Sandbox Code Playgroud)

另一种方法是使用临时表:

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
Run Code Online (Sandbox Code Playgroud)

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

注意:如果您使用的是SQL Server,最好使用以下方法:

WHILE EXISTS(SELECT * FROM #Temp)
Run Code Online (Sandbox Code Playgroud)

使用COUNT将必须触摸表中的每一行,EXISTS唯一需要触摸第一行(请参阅下面的Josef的答案).

  • 如果使用SQL Server,请参阅下面的Josef的答案,对上面的内容进行一些小调整. (10认同)
  • 给这个人一个downvote.他为什么要避免使用光标?他正在谈论迭代**表变量**,而不是传统的表.我不相信游标的正常缺点适用于此.如果确实需要逐行处理(并且正如你所指出的那样,他应该首先确定),那么使用游标比你在这里描述的解决方案要好得多. (3认同)
  • 你能解释为什么这比使用游标更好吗? (2认同)

Jos*_*sef 128

快速说明一下,如果您使用的是SQL Server(2008及更高版本),那么示例包括:

While (Select Count(*) From #Temp) > 0
Run Code Online (Sandbox Code Playgroud)

会更好地服务

While EXISTS(SELECT * From #Temp)
Run Code Online (Sandbox Code Playgroud)

伯爵必须触摸表格中的每一行,EXISTS唯一需要触及第一行.

  • 这不是答案,而是对Martynw回答的评论/提升. (8认同)
  • 这个笔记的内容强制比评论更好的格式化功能,我建议在答案中附加. (7认同)
  • 在 SQL 的更高版本中,查询优化器足够聪明,它知道当你写第一件事时,你实际上是指第二件事,并优化它以避免表扫描。 (2认同)

Tre*_*vor 38

我是这样做的:

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
Run Code Online (Sandbox Code Playgroud)

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


Sei*_*bar 22

像这样定义临时表 -

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
Run Code Online (Sandbox Code Playgroud)

然后这样做 -

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
Run Code Online (Sandbox Code Playgroud)


leo*_*nfo 16

我将如何做到这一点:

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
Run Code Online (Sandbox Code Playgroud)

[编辑]因为我第一次读这个问题时可能跳过了"变量"这个词,这里有一个更新的回复......


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
Run Code Online (Sandbox Code Playgroud)

  • 所以基本上你做了一个游标,但没有游标的所有好处 (4认同)
  • 表?这是一个表VARIABLE - 没有可能的并发访问. (3认同)

小智 10

如果您别无选择,可以逐行创建FAST_FORWARD游标.它将与构建while循环一样快,并且在长期内更容易维护.

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

  • 是啊!正如我在其他地方评论的那样,我还没有看到任何关于为什么**NOT**在案例迭代**表变量**时使用游标的论据.`FAST_FORWARD`cursor是一个很好的解决方案.(给予好评) (2认同)

Gat*_*ler 5

您可以使用 while 循环:

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

    Delete From #TempTable Where x = x
End
Run Code Online (Sandbox Code Playgroud)


小智 5

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

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
Run Code Online (Sandbox Code Playgroud)


小智 5

这将适用于 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 
Run Code Online (Sandbox Code Playgroud)