Cursors, BAD! Table Vars, GOOD!

As of SQL Server 2000, we have had the table variable to use in T-SQL for temporary tables or user defined functions. I like to use table variables wherever I can because I have observed performance problems with CURSORS and #TEMPTable objects.

Stephen Livingstone recently posted some T-SQL that used CURSOR objects to iterate through rows and call another stored procedure. Here is his code:

DECLARE MYCURSOR CURSOR FOR
  (SELECT p1, p2
   FROM  tbl_MyTable
   WHERE p4=123)

 DECLARE @p1 INT
 DECLARE @p2 INT
 DECLARE @p3 INT

 OPEN MYCURSOR
  FETCH NEXT FROM MYCURSOR INTO @p1, @p2, @p3

  WHILE @@FETCH_STATUS = 0
   BEGIN

    -call stored procedure
    exec UpdateTable @p1, @p2, @p3

    FETCH NEXT FROM MYCURSOR

   END

    CLOSE MYCURSOR
    DEALLOCATE MYCURSOR

Here is how I would prefer to write this, assuming that I had to call the internal stored procedure iteratively:

-- set up my in-memory table
declare @myTable table
  
(rowId int identity(1,1),
   p1 int,
   p2 int,
   p3 int)

-- set up row counters and “cursor“ variables
declare @rowId int, @maxRowId int, @p1 int, @p2 int, @p3 int

-- insert the rows into the work table
insert into @myTable
   (p1, p2, p3)
select
   p1, p2, p3
from
   tbl_myTable
where
   p4 = 123

-- determine the start and end rows (RowId is an Identity column, auto-incrementing starting at 1)
-- note, I should check for Null here.
select
   @rowId = min(RowId),
   @MaxRowId = max(RowId)
from @myTable

-- here is the loop
while @RowId <= @MaxRowId begin
   -- fetch the values from the work table
   -- for the current row
   select
      @p1 = p1,
      @p2 = p2,
      @p3 = p3
   from
      @myTable
   where 
      RowId = @RowId

   -- call the stored proc for this row
   exec UpdateTable @p1, @p2, @p3
   -- increment the row counter
   set @RowId = @RowId + 1
end

-- end of procedure

I'm not exactly sure what SQL Server is doing behind the scenes, but here is my theory - @Table variables use a lot less of tempdb (or maybe none at all) than do #TEMPTable and CURSOR objects. I know that #tempTable objects are created in tempdb, and I think that CURSOR objects are created there too. In some performance issues I was troubleshooting with stored procedures, they were deadlocking on tempdb..sysobjects and tempdb..syscolumns, and when I re-wrote them to use @table variables instead of CURSORS, those problems went away.

There are some limitations of @table variables - they are completely locally scoped, they cannot be passed as parameters, and they cannot use INSERT INTO @table EXEC StoredProc. See here and here for some earlier discussion about @Table variable scope.

Mike

PS: My first preference, if this code is being called regularly, is to revisit the stored proc UpdateTable, and either create another stored proc that does the same thing but operates on a set of rows (passing in the criteria for P4, in the example the value for P4 was 123).

CREATE PROC dbo.UpdateTableSet @filter int
as

update tbl_myOthertable
  set ...
from tbl_MyOtherTable mot
join tbl_myTable mt on mot.MotId = mt.ModId
where pt.P4 = @filter

Update: I fixed a boundary bug in the While statement, as pointed out by  

4 Comments

Comments have been disabled for this content.