Cursors in SQL

Row-by-row processing.

Introduction

SQL logo

A cursor in SQL is a database object used to retrieve, manipulate, and navigate through rows of data in a result set one row at a time. Cursors are often used in scenarios where row-by-row processing is necessary, as opposed to set-based processing typical in SQL.

While SQL is designed for handling data in bulk (set-based operations), cursors allow procedural control over individual rows, making them useful in specific cases like complex row-wise operations that cannot be easily achieved with standard SQL queries.

Why Use Cursors?

Cursors are valuable when:

  • You need to process rows one at a time.
  • The logic required for each row is complex.
  • You’re performing operations where set-based SQL solutions are impractical.

In customer management systems, cursors can be useful for tasks such as applying specific discounts to individual customers or updating customer records one by one.

Basic Cursor Syntax

Cursors follow a series of steps for their lifecycle:

  1. Declare the cursor: Define the result set.
  2. Open the cursor: Execute the SQL query and populate the cursor with rows.
  3. Fetch the cursor: Retrieve rows from the result set one at a time.
  4. Process the row(s): Perform the required operations.
  5. Close the cursor: Release the cursor's resources.
  6. Deallocate the cursor: Remove the cursor definition from memory.

The basic syntax for working with cursors is as follows:

DECLARE cursor_name CURSOR FOR
SELECT column1, column2 
FROM TableName;

OPEN cursor_name;

FETCH NEXT FROM cursor_name INTO @variable1, @variable2;

WHILE @@FETCH_STATUS = 0
BEGIN
    -- Process each row
    FETCH NEXT FROM cursor_name INTO @variable1, @variable2;
END;

CLOSE cursor_name;
DEALLOCATE cursor_name;

Let’s consider a scenario where you need to update customer loyalty points based on their total purchases.

Example: Update loyalty points for customers who have made more than $1000 in purchases.

DECLARE CustomerCursor CURSOR FOR
SELECT customer_id, TotalPurchases 
FROM Customers 
WHERE TotalPurchases > 1000;

DECLARE @customer_id INT, @TotalPurchases DECIMAL(10,2);

OPEN CustomerCursor;

FETCH NEXT FROM CustomerCursor INTO @customer_id, @TotalPurchases;

WHILE @@FETCH_STATUS = 0
BEGIN
    -- Add loyalty points (e.g., 10% of total purchases)
    UPDATE Customers
    SET LoyaltyPoints = LoyaltyPoints + (@TotalPurchases * 0.1)
    WHERE customer_id = @customer_id;

    FETCH NEXT FROM CustomerCursor INTO @customer_id, @TotalPurchases;
END;

CLOSE CustomerCursor;
DEALLOCATE CustomerCursor;

In this example, the cursor processes each customer who qualifies for the loyalty points update based on their total purchases.

Cursor Types

MS SQL Server supports various cursor types, each offering different levels of performance and flexibility:

  1. Static Cursors
  • These cursors create a static snapshot of the result set when opened.
  • Changes made to the underlying data after the cursor is opened are not reflected in the cursor.
  1. Dynamic Cursors
  • A dynamic cursor reflects all changes (inserts, updates, deletes) made to the rows in the result set.
  • This type of cursor is slower due to the real-time updates.
  1. Forward-Only Cursors
  • A simple and fast cursor that only allows movement from the first row to the last, in sequential order.
  • This type of cursor is the default and consumes fewer resources.
  1. Keyset-Driven Cursors
  • These cursors track the set of keys that define the rows in the result set. Changes to non-key values are reflected in the cursor.

Cursor Performance Considerations

Cursors are not ideal for large result sets because they operate row-by-row, making them inherently slower than set-based operations. Here are some tips to improve cursor performance:

  • Use cursors sparingly: Try to solve problems with set-based queries when possible.
  • Limit the number of rows: Retrieve only the necessary rows in the cursor.
  • Choose the appropriate cursor type: Use forward-only or static cursors to reduce overhead.

Example: Using a more efficient forward-only cursor for a simple operation.

DECLARE CustomerCursor CURSOR FORWARD_ONLY FOR
SELECT customer_id, TotalPurchases 
FROM Customers 
WHERE TotalPurchases > 1000;

-- The rest of the process follows the same pattern

Alternatives to Cursors

Whenever possible, using set-based SQL is preferable due to better performance. Some alternatives to cursors include:

  • UPDATE with JOIN: Instead of using a cursor to update records one by one, you can often use UPDATE combined with JOIN to update multiple rows at once.

Example: Update customer loyalty points without using a cursor.

UPDATE Customers
SET LoyaltyPoints = LoyaltyPoints + (TotalPurchases * 0.1)
WHERE TotalPurchases > 1000;
  • Common Table Expressions (CTEs): CTEs can sometimes be used to structure complex updates or queries without resorting to cursors.

Error Handling with Cursors

When using cursors, it’s essential to handle errors properly, ensuring resources are released if something goes wrong.

Example: Adding error handling to a cursor.

BEGIN TRY
    DECLARE CustomerCursor CURSOR FOR
    SELECT customer_id, TotalPurchases FROM Customers WHERE TotalPurchases > 1000;

    OPEN CustomerCursor;

    FETCH NEXT FROM CustomerCursor INTO @customer_id, @TotalPurchases;

    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- Processing logic
        FETCH NEXT FROM CustomerCursor INTO @customer_id, @TotalPurchases;
    END;

    CLOSE CustomerCursor;
    DEALLOCATE CustomerCursor;
END TRY
BEGIN CATCH
    IF @@TRANCOUNT > 0 ROLLBACK;
    CLOSE CustomerCursor;
    DEALLOCATE CustomerCursor;
    THROW;
END CATCH;

This example uses a TRY...CATCH block to handle errors gracefully, ensuring that resources are properly freed.

Conclusion

Cursors in SQL are powerful tools for row-by-row processing when set-based operations are insufficient. While they offer flexibility, especially in scenarios like customer management where individual records require attention, they should be used judiciously due to potential performance concerns. When performance is crucial, it’s essential to evaluate whether a cursor is necessary or if set-based SQL or other alternatives can achieve the same result more efficiently.

Asked in Interview

  • What is a Cursor in SQL?
  • What are the different types of Cursors in SQL?
  • When should you use a Cursor in SQL?
  • What are the main operations performed on a Cursor?
  • What are the pros and cons of using Cursors in SQL?
  • What are alternatives to using Cursors in SQL?
  • How do Cursors affect performance, and how can you optimize their use?