Cursors (Transact-SQL)

A Transact-SQL cursor is a control mechanism that enables operations on individual rows of a result set, row by row.

Introduction

While set-based operations are generally preferred in SQL for performance, cursors are sometimes necessary when row-by-row processing is the most straightforward or only feasible approach. Cursors allow you to iterate through a result set, fetch one row at a time, and perform actions on that specific row. This can be useful for complex reporting, data manipulation where each row has unique dependencies, or integration with applications that require sequential data processing.

How Cursors Work

When you declare a cursor, SQL Server creates a temporary result set based on your query. You can then open the cursor, which populates this temporary result set. After opening, you can fetch rows one by one into variables or temporary tables. As you fetch, the cursor's internal pointer moves to the next row. You can also move the pointer backward, to the first or last row, or to a specific row.

Cursor Types

SQL Server supports several types of cursors, each with different characteristics:

Within Transact-SQL cursors, further distinctions are made based on their characteristics:

Cursor Operations

The primary operations for working with cursors are:

Syntax

The basic syntax for declaring and using a cursor:


DECLARE cursor_name CURSOR
    [ [ INSENSITIVE ] [ SCROLL ] | [ FORWARD_ONLY ] ]
    [ STATIC ] [ KEYSET ] [ DYNAMIC ]
    [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
    [ TYPE_WARNING ]
    FOR select_statement
[ ; ]

OPEN cursor_name
[ ; ]

FETCH
    [ NEXT | PRIOR | FIRST | LAST | {ABSOLUTE {n | @n} | RELATIVE {n | @n} } ]
    [ FROM ] cursor_name
    [ INTO @variable_name [ , ...n ] ]
[ ; ]

-- Example of processing a row
-- IF @@FETCH_STATUS = 0
-- BEGIN
--     -- Process the fetched row using @variable_name(s)
--     -- ...
-- END

CLOSE cursor_name
[ ; ]

DEALLOCATE cursor_name
[ ; ]
            

Cursor Attributes Explanation:

Example

This example demonstrates how to declare a cursor to iterate through employees and print their names:


DECLARE @EmployeeName NVARCHAR(100);
DECLARE @Department NVARCHAR(50);

-- Declare a FORWARD_ONLY, READ_ONLY cursor
DECLARE EmployeeCursor CURSOR FOR
SELECT FirstName + ' ' + LastName, Department
FROM Employees
WHERE IsActive = 1;

OPEN EmployeeCursor;

-- Fetch the first row
FETCH NEXT FROM EmployeeCursor INTO @EmployeeName, @Department;

-- Loop through the cursor
WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT 'Employee: ' + @EmployeeName + ' | Department: ' + @Department;

    -- Fetch the next row
    FETCH NEXT FROM EmployeeCursor INTO @EmployeeName, @Department;
END;

CLOSE EmployeeCursor;
DEALLOCATE EmployeeCursor;
            
Note: The @@FETCH_STATUS system function returns the status of the last FETCH statement. A value of 0 indicates success.

Considerations

While powerful, cursors can have performance implications:

Always strive to rewrite cursor logic into set-based operations where possible. Consider using techniques like temporary tables, table variables, or window functions to achieve set-based processing.

Tip: Before implementing a cursor, thoroughly analyze if the task can be accomplished using standard `INSERT`, `UPDATE`, `DELETE` statements that operate on entire sets of rows.