简体   繁体   English

使用游标时如何避免在 T-SQL 中重复 FETCH?

[英]How to avoid duplicated FETCH in T-SQL when using a cursor?

In T-SQL, when iterating results from a cursor, it seems to be common practice to repeat the FETCH statement before the WHILE loop.在 T-SQL 中,当迭代游标的结果时,在WHILE循环之前重复FETCH语句似乎是常见的做法。 The below example from Microsoft :以下来自Microsoft 的示例:

DECLARE Employee_Cursor CURSOR FOR
SELECT EmployeeID, Title FROM AdventureWorks2012.HumanResources.Employee
    WHERE JobTitle = 'Marketing Specialist';
OPEN Employee_Cursor;

FETCH NEXT FROM Employee_Cursor;
WHILE @@FETCH_STATUS = 0
    BEGIN
        FETCH NEXT FROM Employee_Cursor;
    END;
CLOSE Employee_Cursor;
DEALLOCATE Employee_Cursor;
GO

(Notice how FETCH NEXT FROM Employee_Cursor; appears twice.) (注意FETCH NEXT FROM Employee_Cursor;如何出现两次。)

If the FETCH selects into a long list of variables, then we have a large duplicated statement which is both ugly and of course, "non-DRY" code.如果FETCH选择进入一长串变量,那么我们有一个大的重复语句,它既丑陋又当然是“非 DRY”代码。

I'm not aware of a post-condition control-of-flow T-SQL statement so it seems I'd have to resort to a WHILE(TRUE) and then BREAK when @@FETCH_STATUS is not zero.我不知道后置条件控制流 T-SQL 语句,所以当@@FETCH_STATUS不为零时,我似乎不得不求助于WHILE(TRUE)然后BREAK This feels clunky to me.这对我来说很笨拙。

What other options do I have?我还有什么其他选择?

There's a good structure posted online by Chris Oldwood which does it quite elegantly: Chris Oldwood在网上发布了一个很好的结构,它非常优雅:

DECLARE @done bit = 0 

WHILE (@done = 0)  
BEGIN 
  -- Get the next author.  
  FETCH NEXT FROM authors_cursor  
  INTO @au_id, @au_fname, @au_lname  

  IF (@@FETCH_STATUS <> 0) 
  BEGIN 
    SET @done = 1 
    CONTINUE 
  END 

  -- 
  -- stuff done here with inner cursor elided 
  -- 
END

This is what I've resorted to (oh the shame of it):这就是我所采用的(哦,这是耻辱):

WHILE (1=1)
BEGIN
    FETCH NEXT FROM C1 INTO
   @foo,
   @bar,
   @bufar,
   @fubar,
   @bah,
   @fu,
   @foobar,
   @another,
   @column,
   @in,
   @the,
   @long,
   @list,
   @of,
   @variables,
   @used,
   @to,
   @retrieve,
   @all,
   @values,
   @for,
   @conversion

    IF (@@FETCH_STATUS <> 0)
    BEGIN
        BREAK
    END

     -- Use the variables here
END

CLOSE C1
DEALLOCATE C1

You can see why I posted a question.你可以看到我为什么发布了一个问题。 I don't like how the control of flow is hidden in an if statement when it should be in the while .我不喜欢流的控制如何隐藏在if语句中, while它应该在while

The first Fetch shouldn't be a Fetch next , just a fetch .第一个Fetch不应该是Fetch next ,而应该是fetch

Then you're not repeating yourself.那你就没有重复自己了。

I'd spend more effort getting rid of the cursor, and less on DRY dogma, (but if it really matters, you could use a GOTO :) - Sorry, M. Dijkstra)我会花更多的精力摆脱光标,而不是 DRY 教条,(但如果真的很重要,你可以使用GOTO :) - 抱歉,M. Dijkstra)

GOTO Dry
WHILE @@FETCH_STATUS = 0 
BEGIN 
    --- stuff here

Dry:
    FETCH NEXT FROM Employee_Cursor; 
END; 

Here is my humble contribution.这是我卑微的贡献。 Single FETCH statement, no GOTO, no BREAK, no CONTINUE.单个 FETCH 语句,没有 GOTO,没有 BREAK,没有 CONTINUE。

-- Sample table
DROP TABLE IF EXISTS #tblEmployee;
CREATE TABLE #tblEmployee(ID int, Title varchar(100));
INSERT INTO #tblEmployee VALUES (1, 'First One'), (2, 'Second Two'), (3, 'Third Three'), (3, '4th Four');
-- Cursor with one FETCH statement
DECLARE @bEOF bit=0, @sTitle varchar(200), @nID int;
DECLARE cur CURSOR LOCAL FOR SELECT ID, Title FROM #tblEmployee;
OPEN cur;
WHILE @bEOF=0
BEGIN
    FETCH NEXT FROM cur INTO @nID, @sTitle;
    IF @@FETCH_STATUS<>0
        SET @bEOF=1;
    ELSE
    BEGIN
        PRINT Str(@nID)+'. '+@sTitle;
    END;
END;
CLOSE cur;
DEALLOCATE cur;
-- Cleanup
DROP TABLE IF EXISTS #tblEmployee;

It is obvious that a cursor is the pointer to the current row in the recordset.很明显,游标是指向记录集中当前行的指针。 But mere pointing isn't gonna make sense unless it can be used.但是仅仅指点是没有意义的,除非它可以使用。 Here comes the Fetch statement into the scene.这里是场景中的 Fetch 语句。 This takes data from the recordset, stores it in the variable(s) provided.这从记录集中获取数据,将其存储在提供的变量中。 so if you remove the first fetch statement the while loop won't work as there is not "FETCHED" record for manipulation, if you remove the last fetch statement, the "while" will not loop-through.因此,如果您删除第一个 fetch 语句,while 循环将不起作用,因为没有用于操作的“FETCHED”记录,如果您删除最后一个 fetch 语句,“while”将不会循环。

So it is necessary to have both the fetch statement to loop-through the complete recordset.所以有必要同时使用 fetch 语句来循环遍历完整的记录集。

Simply said you can't... that's just how most where statements in SQL work.简单地说你不能......这就是 SQL 中大多数 where 语句的工作方式。 You need to get the first line before the loop and then do it again in the while statement.您需要在循环之前获取第一行,然后在 while 语句中再次执行。

The better question how to get rid of the cursor and try to solve your query without it.更好的问题是如何摆脱游标并尝试在没有它的情况下解决您的查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM