In one of the recent conferences I sat down with a couple of friends—Umachandar Jayachandran (UC) and Ron Talmage; we were trying to resolve a performance problem related to cursors. As UC started writing the cursor code in his laptop, I realized that he used cursor syntax that I wasn’t used to. Syntax that is not new in SQL Server, but that I just wasn’t aware of. Could very well be that I’m one of very few who weren’t aware of this syntax, but for the chance it’s not the case, I decided to share.

When you need to define a local cursor that you will work with only in the context of the local batch, you can define a cursor variable, and work with it the same way you work with regular cursors. The benefit in using a local variable is that once the batch expires, the variable is destroyed, and therefore the cursor is automatically closed and deallocated. So using a cursor variable can save you from writing the two lines of code that close and deallocate the cursor. ;)

As an example, run the following code to create a table called Sales in tempdb and populate it with sample data:

SET NOCOUNT ON;

USE tempdb;

 

IF OBJECT_ID('dbo.Sales', 'U') IS NOT NULL DROP TABLE dbo.Sales;

 

CREATE TABLE dbo.Sales

(

  empid INT      NOT NULL,

  dt    DATETIME NOT NULL,

  qty   INT      NOT NULL,

  CONSTRAINT PK_Sales PRIMARY KEY(empid, dt)

);

GO

 

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(1, '20090212', 5);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(1, '20090213', 11);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(1, '20090214', 19);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(1, '20090215', 19);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(1, '20090216', 3);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(2, '20090212', 13);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(2, '20090213', 23);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(2, '20090214', 2);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(2, '20090215', 17);

INSERT INTO dbo.Sales(empid, dt, qty) VALUES(2, '20090216', 7);

 

Suppose that you need to write code that returns the running total quantity for each employee and day, and decide to use a cursor-based solution. Here’s how your code would look like using a local cursor variable:

DECLARE @Result TABLE

(

  empid  INT,

  dt     DATETIME,

  qty    INT,

  sumqty BIGINT

);

 

DECLARE

  @C        AS CURSOR,

  @empid    AS INT,

  @prvempid AS INT,

  @dt       AS DATETIME,

  @qty      AS INT,

  @sumqty   AS BIGINT;

 

SET @C = CURSOR FAST_FORWARD FOR

  SELECT empid, dt, qty

  FROM dbo.Sales

  ORDER BY empid, dt;

 

OPEN @C;

 

FETCH NEXT FROM @C INTO @empid, @dt, @qty;

SELECT @prvempid = @empid, @sumqty = 0;

 

WHILE @@fetch_status = 0

BEGIN

  IF @empid <> @prvempid SELECT @prvempid = @empid, @sumqty = 0;

  SET @sumqty = @sumqty + @qty;

  INSERT INTO @Result VALUES(@empid, @dt, @qty, @sumqty);

  FETCH NEXT FROM @C INTO @empid, @dt, @qty;

END

 

SELECT * FROM @Result;

Again, this syntax is not new in the product, and this code runs fine even on SQL Server 2000.

Of course, if you have a long batch and are done with the cursor before the batch expires, it is still recommended to close and deallocate the cursor explicitly to free its resources. Otherwise, as programmers we always appreciate any savings in number of keystrokes.

Cheers,

BG