Tip Regarding Cursor Variables
Itzik describes a tip regarding working with cursor variables in the local batch.
May 30, 2009
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;