【问题标题】:SQL Server loop - how do I loop through a set of recordsSQL Server循环 - 我如何循环一组记录
【发布时间】:2014-01-06 21:07:03
【问题描述】:

如何从一个选择中循环遍历一组记录?

例如,假设我有一些记录,我希望循环并对每条记录执行一些操作。这是我选择的原始版本:

select top 1000 * from dbo.table
where StatusID = 7 

谢谢

【问题讨论】:

  • 你想对每条记录做什么?首选是在 SQL 查询中完成工作。除非您需要使用 T-SQL,否则可能需要使用游标。
  • 我会使用光标。
  • 那会很慢 - 是否不可能重写存储的过程或将其中的一些逻辑移出以以基于集合的方式工作?
  • @Funky 存储过程是做什么的?通常可以以基于集合的方式重写代码(即避免循环)。如果您坚持要执行 RBAR 操作 (simple-talk.com/sql/t-sql-programming/…),那么光标就是您要调查的对象。
  • 也许您可以更详细地解释您将如何处理这些数据。在大多数情况下,您可以轻松编写一个 SQL 查询,该查询将在一个操作中完成您需要完成的工作,而不是遍历单个记录。

标签: sql sql-server


【解决方案1】:

如果你需要做一些迭代的事情,这就是我一直在做的事情......但首先寻找集合操作是明智的。另外,不要因为不想学习游标而这样做。

select top 1000 TableID
into #ControlTable 
from dbo.table
where StatusID = 7

declare @TableID int

while exists (select * from #ControlTable)
begin

    select top 1 @TableID = TableID
    from #ControlTable
    order by TableID asc

    -- Do something with your TableID

    delete #ControlTable
    where TableID = @TableID

end

drop table #ControlTable

【讨论】:

  • 使用 CURSOR(见下面的答案)似乎是一个更优雅的解决方案。
  • 为什么这个答案比游标解决方案有更多的赞成票?
  • @ataravati 因为这个解决方案对许多程序员来说比游标更清晰地读取。游标的语法对某些人来说相当尴尬。
  • 这实际上回答了原始问题,因为它可以让您遍历整行;而 cursor 可以让您遍历一行中的特定列。
  • Cursorswhile 语句更冗长,但它们更优化。我不建议使用while 语句,尤其是在循环条件中有子查询的情况下。
【解决方案2】:

我认为这是迭代项目的简单方法示例。

declare @cateid int
select CateID into [#TempTable] from Category where GroupID = 'STOCKLIST'

while (select count(*) from #TempTable) > 0
begin
    select top 1 @cateid = CateID from #TempTable
    print(@cateid)

    --DO SOMETHING HERE

    delete #TempTable where CateID = @cateid
end

drop table #TempTable

【讨论】:

    【解决方案3】:

    通过使用光标,您可以轻松地单独遍历记录并单独打印记录或作为包含所有记录的单个消息。

    DECLARE @CustomerID as INT;
    declare @msg varchar(max)
    DECLARE @BusinessCursor as CURSOR;
    
    SET @BusinessCursor = CURSOR FOR
    SELECT CustomerID FROM Customer WHERE CustomerID IN ('3908745','3911122','3911128','3911421')
    
    OPEN @BusinessCursor;
        FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @msg = '{
                  "CustomerID": "'+CONVERT(varchar(10), @CustomerID)+'",
                  "Customer": {
                    "LastName": "LastName-'+CONVERT(varchar(10), @CustomerID) +'",
                    "FirstName": "FirstName-'+CONVERT(varchar(10), @CustomerID)+'",    
                  }
                }|'
            print @msg
        FETCH NEXT FROM @BusinessCursor INTO @CustomerID;
    END
    

    【讨论】:

    • 这看起来很有趣。我想知道@标识符是什么意思。
    • @ 只是为了区分变量。
    • 这绝对很有趣。但请赐教:---> 'SET @msg ...' 部分如何从客户那里获取数据?以及如何将字段标识为 LastName 或 FirstName 返回值?从哪个变量?我不明白那部分,请您解释一下
    【解决方案4】:

    sam yi's answer的小改动(为了更好的可读性):

    select top 1000 TableID
    into #ControlTable 
    from dbo.table
    where StatusID = 7
    
    declare @TableID int
    
    while exists (select * from #ControlTable)
    begin
    
        select @TableID = (select top 1 TableID
                           from #ControlTable
                           order by TableID asc)
    
        -- Do something with your TableID
    
        delete #ControlTable
        where TableID = @TableID
    
    end
    
    drop table #ControlTable
    

    【讨论】:

    • @bluish,这个答案正在纠正 sam yi 的答案。此更正主要在select @TableID = (...) 语句中。
    • 我觉得这个问题一定要选这个答案
    【解决方案5】:

    您可以选择对数据进行排名并添加 ROW_NUMBER 并在迭代数据集时倒计时至零。

    -- Get your dataset and rank your dataset by adding a new row_number
    SELECT  TOP 1000 A.*, ROW_NUMBER() OVER(ORDER BY A.ID DESC) AS ROW
    INTO #TEMPTABLE 
    FROM DBO.TABLE AS A
    WHERE STATUSID = 7;
    
    --Find the highest number to start with
    DECLARE @COUNTER INT = (SELECT MAX(ROW) FROM #TEMPTABLE);
    DECLARE @ROW INT;
    
    -- Loop true your data until you hit 0
    WHILE (@COUNTER != 0)
    BEGIN
    
        SELECT @ROW = ROW
        FROM #TEMPTABLE
        WHERE ROW = @COUNTER
        ORDER BY ROW DESC
    
        --DO SOMTHING COOL  
    
        -- SET your counter to -1
        SET @COUNTER = @ROW -1
    END
    
    DROP TABLE #TEMPTABLE
    

    【讨论】:

    • ORDER BY 在循环内没用
    【解决方案6】:

    通过像这样使用 T-SQL 和游标:

    DECLARE @MyCursor CURSOR;
    DECLARE @MyField YourFieldDataType;
    BEGIN
        SET @MyCursor = CURSOR FOR
        select top 1000 YourField from dbo.table
            where StatusID = 7      
    
        OPEN @MyCursor 
        FETCH NEXT FROM @MyCursor 
        INTO @MyField
    
        WHILE @@FETCH_STATUS = 0
        BEGIN
          /*
             YOUR ALGORITHM GOES HERE   
          */
          FETCH NEXT FROM @MyCursor 
          INTO @MyField 
        END; 
    
        CLOSE @MyCursor ;
        DEALLOCATE @MyCursor;
    END;
    

    【讨论】:

    • 正确的做法是重写进程,使其不需要循环。循环在数据库中是一个非常糟糕的选择。
    • 也许你是对的,但是在我写答案时问题中给出的信息,用户只想遍历一组数据......而光标是一种方法它。
    • 游标只是一种工具——它们通常没有对错。观察表现并做出决定。这个答案(光标)是一种可能的选择。您还可以使用 WHILE LOOP、CTE 等。
    • @FrenkyB 是的,你可以。往这边看……stackoverflow.com/questions/11035187/…
    • 恭喜,您的解决方案甚至在 msdn 上:msdn.microsoft.com/en-us/library/… 我真的很喜欢您使用字段数据类型的方式。
    【解决方案7】:

    这样我们可以迭代到表数据中。

    DECLARE @_MinJobID INT
    DECLARE @_MaxJobID INT
    CREATE  TABLE #Temp (JobID INT)
    
    INSERT INTO #Temp SELECT * FROM DBO.STRINGTOTABLE(@JobID,',')
    SELECT @_MinJID = MIN(JobID),@_MaxJID = MAX(JobID)  FROM #Temp
    
        WHILE @_MinJID <= @_MaxJID
        BEGIN
    
            INSERT INTO Mytable        
            (        
                JobID,        
            )        
    
            VALUES        
            (        
                @_MinJobID,        
            ) 
    
            SET @_MinJID = @_MinJID + 1;
        END
    
    DROP TABLE #Temp
    

    STRINGTOTABLE 是用户定义函数,它将解析逗号分隔的数据并返回表。谢谢

    【讨论】:

      【解决方案8】:

      如果您可以使用临时表,这只是另一种方法。我已经亲自测试过,它不会导致任何异常(即使临时表没有任何数据。)

      CREATE TABLE #TempTable
      (
          ROWID int identity(1,1) primary key,
          HIERARCHY_ID_TO_UPDATE int,
      )
      
      --create some testing data
      --INSERT INTO #TempTable VALUES(1)
      --INSERT INTO #TempTable VALUES(2)
      --INSERT INTO #TempTable VALUES(4)
      --INSERT INTO #TempTable VALUES(6)
      --INSERT INTO #TempTable VALUES(8)
      
      DECLARE @MAXID INT, @Counter INT
      
      SET @COUNTER = 1
      SELECT @MAXID = COUNT(*) FROM #TempTable
      
      WHILE (@COUNTER <= @MAXID)
      BEGIN
          --DO THE PROCESSING HERE 
          SELECT @HIERARCHY_ID_TO_UPDATE = PT.HIERARCHY_ID_TO_UPDATE
          FROM #TempTable AS PT
          WHERE ROWID = @COUNTER
      
          SET @COUNTER = @COUNTER + 1
      END
      
      
      IF (OBJECT_ID('tempdb..#TempTable') IS NOT NULL)
      BEGIN
          DROP TABLE #TempTable
      END
      

      【讨论】:

      • 这真的很奇怪。它包含很多错误,还使用了两个变量,其中一个从 1 到 COUNT(*),第二个从 COUNT(*) 到 1 很奇怪。
      • 变量 MAXID 用于循环遍历。变量 COUNTER 用于对表中的特定记录执行操作。如果我读到它谈到的问题是“有一些我希望循环遍历的记录并对每条记录做一些事情”。我可能错了,但请指出@DAWID 上面的问题
      • 我认为在代码中使用这些变量的方式很明显。您可以只拥有WHILE (@COUTNER &lt;= @ROWID),并且不需要在每次迭代中递减@ROWID。顺便说一句,如果您的表中的 ROWIDs 不连续(某些行之前已被删除),会发生什么情况。
      • 你什么时候建议使用临时表而不是使用光标?这仅仅是一种设计选择,还是有更好的性能?
      猜你喜欢
      • 2019-04-27
      • 2018-10-01
      • 2023-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-01
      • 2018-06-20
      相关资源
      最近更新 更多