我可以在T-SQL中遍历表变量吗?


68

无论如何,在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

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

任何帮助是极大的赞赏。



3
“游标似乎不如表变量灵活”。这句话没有任何意义。它们是完全不同的东西。您当然可以使用游标遍历表变量。
马丁·史密斯

Answers:


106

向您的表变量添加一个标识,然后从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

4
这似乎是最简单的。谢谢!
Kuyenda,2009年

14
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

信不信由你,它实际上比使用游标更有效和高效。


为什么在循环中每次选择最大值?
KM。

您可以选择一次并将其轻松存储到变量中……这只是短一些的击键。
贾斯汀·尼斯纳

1
为什么在循环中每次选择最大值?结果,您必须在每次迭代中两次击中表变量。如果从表变量填充中捕获@@ ROWCOUNT,则可以删除WHILE()中的SELECT MAX(),就像我在回答中所做的那样。
KM。

10

我的两分钱。.从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

这是一个更好的解决方案,因为它不依赖于表变量的内容,因此是公认的答案。
Beerwin

6

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

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


这实际上就是为什么我要使用表变量而不是游标的原因。我通常会在表变量上寻找一种使用JOIN来获得预期结果的方法,但是如果我找不到使用JOIN的方法,那么我可以回到该表变量的循环中。但是我同意,基于集合的方法是最好的。
Kuyenda

在表变量上循环并不比游标好。实际上,情况实际上可能更糟。将代码从游标更改为循环的唯一真正好处是“吹牛”。例如:“我的代码中没有任何游标”。
乔治·马斯特罗斯

6

看起来像这个演示:

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

3

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

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的观点很重要-必要时仅执行循环。


2

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

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次,但效率不高
KM。

2

我不知道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

感谢您的帮助!


2
您的代码:DECLARE cursor1 CURSOR FOR @ table1 OPEN cursor1将不起作用。游标必须在其定义中具有SELECT,就像您的第二个代码示例一样。如果进行一些测试,您会发现不使用游标的循环比使用游标的循环更快。
KM。

1

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

    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

0

以下存储过程在表变量中循环,并按升序打印。本示例使用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查询窗口中显示的结果如下所示:

PrintSequenceSeries的结果

返回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

0

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.