【问题标题】:Can I loop through a table variable in T-SQL?我可以遍历 T-SQL 中的表变量吗?
【发布时间】:2010-12-07 09:13:38
【问题描述】:

在 T-SQL 中是否有遍历表变量的方法?

DECLARE @table1 TABLE ( col1 int )  
INSERT into @table1 SELECT col1 FROM table2

我也使用游标,但游标似乎不如表变量灵活。

DECLARE cursor1 CURSOR  
    FOR SELECT col1 FROM table2  
OPEN cursor1  
FETCH NEXT FROM cursor1

我希望能够以与游标相同的方式使用表变量。这样我就可以在过程的一部分中对表变量执行一些查询,然后为表变量中的每一行执行一些代码。

非常感谢任何帮助。

【问题讨论】:

标签: sql sql-server tsql database-cursor table-variable


【解决方案1】:
DECLARE @table1 TABLE (
    idx int identity(1,1),
    col1 int )

DECLARE @counter int

SET @counter = 1

WHILE(@counter < SELECT MAX(idx) FROM @table1)
BEGIN
    DECLARE @colVar INT

    SELECT @colVar = col1 FROM @table1 WHERE idx = @counter

    -- Do your work here

    SET @counter = @counter + 1
END

信不信由你,这实际上比使用游标更高效、更高效。

【讨论】:

  • 为什么每次循环都选择最大值?
  • 您可以选择一次并将其存储在一个变量中很容易......这只是缩短了几个按键。
  • 为什么在循环中每次都选择最大值?结果,您必须每次迭代两次点击表变量。如果您从表变量人口中捕获@@ROWCOUNT,您可以删除 WHILE() 中的 SELECT MAX(),就像我在回答中所做的那样。
【解决方案2】:

您可以循环遍历表变量,也可以通过光标浏览它。这就是我们通常所说的 RBAR——发音为 Reebar,意思是 Row-By-Agonizing-Row。

我建议为您的问题找到一个基于 SET 的答案(我们可以提供帮助)并尽可能远离 rbar。

【讨论】:

  • 这实际上是我想使用表变量而不是游标的原因。我通常会寻找一种在表变量上使用 JOIN 来获得预期结果的方法,但如果我找不到使用 JOIN 的方法,那么我可以在同一个表变量上使用循环。但我同意,基于集合是最好的。
  • 在表变量上循环并不比游标好。事实上,它实际上可能更糟。将代码从游标更改为循环的唯一真正好处是“吹牛的权利”。例如:“我的代码中没有任何光标”。
【解决方案3】:

这是另一个答案,类似于贾斯汀的答案,但不需要身份或聚合,只需一个主(唯一)密钥。

declare @table1 table(dataKey int, dataCol1 varchar(20), dataCol2 datetime)
declare @dataKey int
while exists select 'x' from @table1
begin
    select top 1 @dataKey = dataKey 
    from @table1 
    order by /*whatever you want:*/ dataCol2 desc

    -- do processing

    delete from @table1 where dataKey = @dataKey
end

【讨论】:

  • 每次迭代你都会点击表变量 3 次,效率不会那么高
【解决方案4】:

为您的表变量添加一个标识,然后从 1 到 INSERT-SELECT 的 @@ROWCOUNT 进行简单循环。

试试这个:

DECLARE @RowsToProcess  int
DECLARE @CurrentRow     int
DECLARE @SelectCol1     int

DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int )  
INSERT into @table1 (col1) SELECT col1 FROM table2
SET @RowsToProcess=@@ROWCOUNT

SET @CurrentRow=0
WHILE @CurrentRow<@RowsToProcess
BEGIN
    SET @CurrentRow=@CurrentRow+1
    SELECT 
        @SelectCol1=col1
        FROM @table1
        WHERE RowID=@CurrentRow

    --do your thing here--

END

【讨论】:

  • 这似乎是最简单的。谢谢!
【解决方案5】:

这是我的变种。几乎和所有其他人一样,但我只使用一个变量来管理循环。

DECLARE
  @LoopId  int
 ,@MyData  varchar(100)

DECLARE @CheckThese TABLE
 (
   LoopId  int  not null  identity(1,1)
  ,MyData  varchar(100)  not null
 )


INSERT @CheckThese (MyData)
 select MyData from MyTable
 order by DoesItMatter

SET @LoopId = @@rowcount

WHILE @LoopId > 0
 BEGIN
    SELECT @MyData = MyData
     from @CheckThese
     where LoopId = @LoopId

    --  Do whatever

    SET @LoopId = @LoopId - 1
 END

Raj More 的观点很重要——仅在必要时执行循环。

【讨论】:

    【解决方案6】:

    我不知道 WHILE 结构。

    但是,带有表变量的 WHILE 结构看起来类似于使用 CURSOR,因为您仍然必须根据行 IDENTITY 将行选择到变量中,这实际上是一个 FETCH。

    使用 WHERE 和以下类似的有什么区别吗?

    DECLARE @table1 TABLE ( col1 int )  
    INSERT into @table1 SELECT col1 FROM table2
    
    DECLARE cursor1 CURSOR  
        FOR @table1
    OPEN cursor1  
    FETCH NEXT FROM cursor1
    

    我不知道这是否可能。我想你可能必须这样做:

    DECLARE cursor1 CURSOR  
        FOR SELECT col1 FROM @table1
    OPEN cursor1  
    FETCH NEXT FROM cursor1
    

    感谢您的帮助!

    【讨论】:

    • 您的代码:DECLARE cursor1 CURSOR FOR @table1 OPEN cursor1 将不起作用。游标的定义中必须有一个 SELECT ,就像您的第二个代码示例一样。如果你做一些测试,你会发现不使用游标循环比使用游标循环更快。
    【解决方案7】:

    这是我的相同解决方案的版本...

        declare @id int
    
            SELECT @id = min(fPat.PatientID)
            FROM tbPatients fPat
            WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0)
    
    while @id is not null
    begin
        SELECT fPat.PatientID, fPat.InsNotes
        FROM tbPatients fPat
        WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0) AND fPat.PatientID=@id
    
        SELECT @id = min(fPat.PatientID)
        FROM tbPatients fPat
        WHERE (fPat.InsNotes is not null AND DataLength(fPat.InsNotes)>0)AND fPat.PatientID>@id
    
    end
    

    【讨论】:

      【解决方案8】:

      看起来像这个演示:

      DECLARE @vTable TABLE (IdRow int not null primary key identity(1,1),ValueRow int);
      
      -------Initialize---------
      insert into @vTable select 345;
      insert into @vTable select 795;
      insert into @vTable select 565;
      ---------------------------
      
      DECLARE @cnt int = 1;
      DECLARE @max int = (SELECT MAX(IdRow) FROM @vTable);
      
      WHILE @cnt <= @max
      BEGIN
          DECLARE @tempValueRow int = (Select ValueRow FROM @vTable WHERE IdRow = @cnt);
      
          ---work demo----
          print '@tempValueRow:' + convert(varchar(10),@tempValueRow);
          print '@cnt:' + convert(varchar(10),@cnt);
          print'';
          --------------
      
          set @cnt = @cnt+1;
      END
      

      没有 idRow 的版本,使用 ROW_NUMBER

          DECLARE @vTable TABLE (ValueRow int);
      -------Initialize---------
      insert into @vTable select 345;
      insert into @vTable select 795;
      insert into @vTable select 565;
      ---------------------------
      
      DECLARE @cnt int = 1;
      DECLARE @max int = (select count(*) from @vTable);
      
      WHILE @cnt <= @max
      BEGIN
          DECLARE @tempValueRow int = (
              select ValueRow 
              from (select ValueRow
                  , ROW_NUMBER() OVER(ORDER BY (select 1)) as RowId 
                  from @vTable
              ) T1 
          where t1.RowId = @cnt
          );
      
          ---work demo----
          print '@tempValueRow:' + convert(varchar(10),@tempValueRow);
          print '@cnt:' + convert(varchar(10),@cnt);
          print'';
          --------------
      
          set @cnt = @cnt+1;
      END
      

      【讨论】:

        【解决方案9】:

        我的两分钱.. 根据 KM. 的回答,如果您想删除一个变量,您可以在 @RowsToProcess 上倒计时而不是向上计数。

        DECLARE @RowsToProcess  int;
        
        DECLARE @table1 TABLE (RowID int not null primary key identity(1,1), col1 int )  
        INSERT into @table1 (col1) SELECT col1 FROM table2
        SET @RowsToProcess = @@ROWCOUNT 
        
        WHILE @RowsToProcess > 0 -- Countdown
        BEGIN
            SELECT *
                FROM @table1
                WHERE RowID=@RowsToProcess
        
            --do your thing here--
        
            SET @RowsToProcess = @RowsToProcess - 1; -- Countdown
        END
        

        【讨论】:

        • 这是一个更好的解决方案,因为它不依赖于表变量的内容。
        【解决方案10】:

        以下存储过程循环遍历表变量并按升序打印它。此示例使用 WHILE LOOP。

        CREATE PROCEDURE PrintSequenceSeries 
            -- Add the parameters for the stored procedure here
            @ComaSeperatedSequenceSeries nVarchar(MAX)  
        AS
        BEGIN
            -- SET NOCOUNT ON added to prevent extra result sets from
            -- interfering with SELECT statements.
            SET NOCOUNT ON;
        
            DECLARE @SERIES_COUNT AS INTEGER
            SELECT @SERIES_COUNT = COUNT(*) FROM PARSE_COMMA_DELIMITED_INTEGER(@ComaSeperatedSequenceSeries, ',')  --- ORDER BY ITEM DESC
        
            DECLARE @CURR_COUNT AS INTEGER
            SET @CURR_COUNT = 1
        
            DECLARE @SQL AS NVARCHAR(MAX)
        
            WHILE @CURR_COUNT <= @SERIES_COUNT
            BEGIN
                SET @SQL = 'SELECT TOP 1 T.* FROM ' + 
                    '(SELECT TOP ' + CONVERT(VARCHAR(20), @CURR_COUNT) + ' * FROM PARSE_COMMA_DELIMITED_INTEGER( ''' + @ComaSeperatedSequenceSeries + ''' , '','') ORDER BY ITEM ASC) AS T ' +
                    'ORDER BY T.ITEM DESC '
                PRINT @SQL 
                EXEC SP_EXECUTESQL @SQL 
                SET @CURR_COUNT = @CURR_COUNT + 1
            END;
        

        以下语句执行存储过程:

        EXEC  PrintSequenceSeries '11,2,33,14,5,60,17,98,9,10'
        

        SQL查询窗口显示的结果如下:

        返回TABLE变量的函数PARSE_COMMA_DELIMITED_INTEGER()如下所示:

        CREATE FUNCTION [dbo].[parse_comma_delimited_integer]
                (
                    @LIST       VARCHAR(8000), 
                    @DELIMITER  VARCHAR(10) = ',
                    '
                )
        
                -- TABLE VARIABLE THAT WILL CONTAIN VALUES
                RETURNS @TABLEVALUES TABLE 
                (
                    ITEM INT
                )
                AS
                BEGIN 
                    DECLARE @ITEM VARCHAR(255)
        
                    /* LOOP OVER THE COMMADELIMITED LIST */
                    WHILE (DATALENGTH(@LIST) > 0)
                        BEGIN 
                            IF CHARINDEX(@DELIMITER,@LIST) > 0
                                BEGIN
                                    SELECT @ITEM = SUBSTRING(@LIST,1,(CHARINDEX(@DELIMITER, @LIST)-1))
                                    SELECT @LIST =  SUBSTRING(@LIST,(CHARINDEX(@DELIMITER, @LIST) +
                                    DATALENGTH(@DELIMITER)),DATALENGTH(@LIST))
                                END
                            ELSE
                                BEGIN
                                    SELECT @ITEM = @LIST
                                    SELECT @LIST = NULL
                                END
        
                            -- INSERT EACH ITEM INTO TEMP TABLE
                            INSERT @TABLEVALUES 
                            (
                                ITEM
                            )
                            SELECT ITEM = CONVERT(INT, @ITEM) 
                        END
                RETURN
                END
        

        【讨论】:

          【解决方案11】:

          Select Top 1 无需任何顺序/顺序即可轻松解决。

          Create Function Test_Range()
          Returns
          @Result Table (ID Int)
          As
          Begin
          
          Declare @ID Varchar(10) = ''
          Declare @Rows Int, @Row Int = 0
          Declare @Num Int, @RangeTo Int
          
          Declare @RangeTable Table (ID Varchar(10), RangeFrom Int, RangeTo Int)
          Insert Into @RangeTable Values ('A', 1, 10)
          Insert Into @RangeTable Values ('B', 25,30)
          
          Set @Rows = (Select Count(*) From @RangeTable)
          
          While @Row <= @Rows
          Begin
              Set @Row = @Row + 1
              Select Top 1 @ID = ID, @Num = RangeFrom, @RangeTo = RangeTo  From @RangeTable
              Where ID > @ID
              While @Num <= @RangeTo
              Begin
                  Insert Into @Result Values (@Num)
                  Set @Num = @Num + 1
              End
          End
          Return
          End
          

          【讨论】:

            猜你喜欢
            • 2012-11-07
            • 2013-01-17
            • 1970-01-01
            • 2016-04-15
            • 2021-10-03
            • 2018-10-17
            • 1970-01-01
            • 2018-03-08
            • 1970-01-01
            相关资源
            最近更新 更多